cycler: delete and chill tests; refactor the interface passing

BUG=chromium:1087962
TEST=ran these tests

Change-Id: I01aa1469447772cd5b697d539921e21184f0f0ff
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/infra/go/+/2277222
Tested-by: George Engelbrecht <engeg@google.com>
Auto-Submit: George Engelbrecht <engeg@google.com>
Commit-Queue: Sean Abraham <seanabraham@chromium.org>
Reviewed-by: Sean Abraham <seanabraham@chromium.org>
diff --git a/cmd/cycler/effects/chill.go b/cmd/cycler/effects/chill.go
index fcebbe9..cd1a0c9 100644
--- a/cmd/cycler/effects/chill.go
+++ b/cmd/cycler/effects/chill.go
@@ -21,10 +21,6 @@
 	"cloud.google.com/go/storage"
 )
 
-// Real or mock actor, non-test invocations use util.objectChangeStorageClass
-type ChillEffectActor func(context.Context, *storage.Client, *storage.ObjectAttrs,
-	cycler_pb.ChillEffectConfiguration_EnumStorageClass) error
-
 func (ce ChillEffect) DefaultActor() interface{} {
 	return objectChangeStorageClass
 }
@@ -32,7 +28,9 @@
 // ChillEffect runtime and configuration state.
 type ChillEffect struct {
 	Config cycler_pb.ChillEffectConfiguration `json:"ChillEffectConfiguration"`
-	Actor  ChillEffectActor
+	// Real or mock actor, non-test invocations use util.objectChangeStorageClass
+	actor func(ctx context.Context, client *storage.Client, srcAttr *storage.ObjectAttrs,
+		toStorageClass cycler_pb.ChillEffectConfiguration_EnumStorageClass) error
 }
 
 // Init the chill effect.
@@ -52,7 +50,9 @@
 	CheckMutationAllowed(checks)
 
 	ce.Config = orig
-	ce.Actor = actor.(ChillEffectActor)
+	ce.actor = actor.(func(ctx context.Context, client *storage.Client, srcAttr *storage.ObjectAttrs,
+		toStorageClass cycler_pb.ChillEffectConfiguration_EnumStorageClass) error)
+
 }
 
 // Enact does the move operation on the attr, _this deletes the old object_!
@@ -81,7 +81,7 @@
 
 // Internal copy object command for google storage.
 func (ce *ChillEffect) chillObject(ctx context.Context, client *storage.Client, attr *storage.ObjectAttrs) error {
-	return ce.Actor(ctx, client, attr, ce.Config.ToStorageClass)
+	return ce.actor(ctx, client, attr, ce.Config.ToStorageClass)
 }
 
 // ChillResult defines all outputs of a move effect.
diff --git a/cmd/cycler/effects/chill_test.go b/cmd/cycler/effects/chill_test.go
index 9eebe72..43e4d38 100644
--- a/cmd/cycler/effects/chill_test.go
+++ b/cmd/cycler/effects/chill_test.go
@@ -13,7 +13,7 @@
 	cycler_pb "go.chromium.org/chromiumos/infra/proto/go/cycler"
 )
 
-func getChillMock(t *testing.T) ChillEffectActor {
+func getChillMock(t *testing.T) interface{} {
 	return func(ctx context.Context, client *storage.Client, srcAttr *storage.ObjectAttrs,
 		toStorageClass cycler_pb.ChillEffectConfiguration_EnumStorageClass) error {
 		if toStorageClass != cycler_pb.ChillEffectConfiguration_COLDLINE {
diff --git a/cmd/cycler/effects/delete.go b/cmd/cycler/effects/delete.go
index e3081e3..ea77a7d 100644
--- a/cmd/cycler/effects/delete.go
+++ b/cmd/cycler/effects/delete.go
@@ -18,9 +18,6 @@
 	"cloud.google.com/go/storage"
 )
 
-// Real or mock actor, non-test invocations use util.objectDelete.
-type DeleteEffectActor func(ctx context.Context, client *storage.Client, srcAttr *storage.ObjectAttrs) error
-
 func (de DeleteEffect) DefaultActor() interface{} {
 	return objectDelete
 }
@@ -28,7 +25,8 @@
 // DeleteEffect runtime and configuration state.
 type DeleteEffect struct {
 	Config cycler_pb.DeleteEffectConfiguration `json:"DeleteEffectConfiguration"`
-	Actor  DeleteEffectActor
+	// Real or mock actor, non-test invocations use util.objectBucketToBucket.
+	actor func(ctx context.Context, client *storage.Client, srcAttr *storage.ObjectAttrs) error
 }
 
 // Init the DeleteEffect
@@ -43,7 +41,7 @@
 	CheckMutationAllowed(checks)
 
 	de.Config = orig
-	de.Actor = actor.(DeleteEffectActor)
+	de.actor = actor.(func(ctx context.Context, client *storage.Client, srcAttr *storage.ObjectAttrs) error)
 }
 
 // Enact does the delete operation on the attr.
@@ -68,7 +66,7 @@
 
 // Internal delete object command for google storage.
 func (de *DeleteEffect) deleteObject(ctx context.Context, client *storage.Client, attr *storage.ObjectAttrs) error {
-	return de.Actor(ctx, client, attr)
+	return de.actor(ctx, client, attr)
 }
 
 // DeleteResult defines all outputs of a delet eeffect.
diff --git a/cmd/cycler/effects/delete_test.go b/cmd/cycler/effects/delete_test.go
index 034105d..3573607 100644
--- a/cmd/cycler/effects/delete_test.go
+++ b/cmd/cycler/effects/delete_test.go
@@ -13,7 +13,7 @@
 	cycler_pb "go.chromium.org/chromiumos/infra/proto/go/cycler"
 )
 
-func getDeleteMock(t *testing.T) DeleteEffectActor {
+func getDeleteMock(t *testing.T) interface{} {
 	return func(ctx context.Context, client *storage.Client, srcAttr *storage.ObjectAttrs) error {
 		if srcAttr.Bucket != "test_bucket" || srcAttr.Name != "test_object.txt" {
 			t.Error("The bucket or object name did not match configured.")
diff --git a/cmd/cycler/effects/duplicate.go b/cmd/cycler/effects/duplicate.go
index 800c25d..8e35657 100644
--- a/cmd/cycler/effects/duplicate.go
+++ b/cmd/cycler/effects/duplicate.go
@@ -18,10 +18,6 @@
 	"cloud.google.com/go/storage"
 )
 
-// Real or mock actor, non-test invocations use util.objectBucketToBucket.
-type DuplicateEffectActor func(ctx context.Context, client *storage.Client, srcAttr *storage.ObjectAttrs,
-	dstBucket string, prefix string, deleteAfter bool) error
-
 func (de DuplicateEffect) DefaultActor() interface{} {
 	return objectBucketToBucket
 }
@@ -29,7 +25,9 @@
 // DuplicateEffect runtime and configuration state.
 type DuplicateEffect struct {
 	Config cycler_pb.DuplicateEffectConfiguration `json:"DuplicateEffectConfiguration"`
-	Actor  DuplicateEffectActor
+	// Real or mock actor, non-test invocations use util.objectBucketToBucket.
+	actor func(ctx context.Context, client *storage.Client, srcAttr *storage.ObjectAttrs,
+		dstBucket string, prefix string, deleteAfter bool) error
 }
 
 // Init the DuplicateEffect, duplicate doesn't mutate so skip checks.
@@ -45,7 +43,8 @@
 	CheckMutationAllowed(checks)
 
 	de.Config = orig
-	de.Actor = actor.(DuplicateEffectActor)
+	de.actor = actor.(func(ctx context.Context, client *storage.Client, srcAttr *storage.ObjectAttrs,
+		dstBucket string, prefix string, deleteAfter bool) error)
 }
 
 // Enact does the duplicate operation on the attr, does not mutate existing object.
@@ -70,7 +69,7 @@
 
 // Internal duplicate object command for google storage.
 func (de *DuplicateEffect) duplicateObject(ctx context.Context, client *storage.Client, attr *storage.ObjectAttrs) error {
-	return de.Actor(ctx, client, attr, de.Config.DestinationBucket, de.Config.DestinationPrefix, false)
+	return de.actor(ctx, client, attr, de.Config.DestinationBucket, de.Config.DestinationPrefix, false)
 }
 
 // DuplicateResult defines all outputs of an echo effect.
diff --git a/cmd/cycler/effects/duplicate_test.go b/cmd/cycler/effects/duplicate_test.go
index 20bbf79..88aed03 100644
--- a/cmd/cycler/effects/duplicate_test.go
+++ b/cmd/cycler/effects/duplicate_test.go
@@ -13,7 +13,7 @@
 	cycler_pb "go.chromium.org/chromiumos/infra/proto/go/cycler"
 )
 
-func getDuplicateMock(t *testing.T) DuplicateEffectActor {
+func getDuplicateMock(t *testing.T) interface{} {
 	return func(ctx context.Context, client *storage.Client, srcAttr *storage.ObjectAttrs,
 		dstBucket string, prefix string, deleteAfter bool) error {
 		if deleteAfter == true {
diff --git a/cmd/cycler/effects/move.go b/cmd/cycler/effects/move.go
index 8042fde..5a4a30f 100644
--- a/cmd/cycler/effects/move.go
+++ b/cmd/cycler/effects/move.go
@@ -18,10 +18,6 @@
 	"cloud.google.com/go/storage"
 )
 
-// Real or mock actor, non-test invocations use util.objectBucketToBucket.
-type MoveEffectActor func(ctx context.Context, client *storage.Client, srcAttr *storage.ObjectAttrs,
-	dstBucket string, prefix string, deleteAfter bool) error
-
 func (me MoveEffect) DefaultActor() interface{} {
 	return objectBucketToBucket
 }
@@ -29,7 +25,9 @@
 // MoveEffect runtime and configuration state.
 type MoveEffect struct {
 	Config cycler_pb.MoveEffectConfiguration `json:"MoveEffectConfiguration"`
-	Actor  MoveEffectActor
+	// Real or mock actor, non-test invocations use util.objectBucketToBucket.
+	actor func(ctx context.Context, client *storage.Client, srcAttr *storage.ObjectAttrs,
+		dstBucket string, prefix string, deleteAfter bool) error
 }
 
 // MoveEffectConfig configuration.
@@ -49,7 +47,8 @@
 	CheckMutationAllowed(checks)
 
 	me.Config = orig
-	me.Actor = actor.(MoveEffectActor)
+	me.actor = actor.(func(ctx context.Context, client *storage.Client, srcAttr *storage.ObjectAttrs,
+		dstBucket string, prefix string, deleteAfter bool) error)
 }
 
 // Enact does the move operation on the attr, _this deletes the old object_!
@@ -78,7 +77,7 @@
 
 // Internal move object command for google storage.
 func (me *MoveEffect) moveObject(ctx context.Context, client *storage.Client, attr *storage.ObjectAttrs) error {
-	return me.Actor(ctx, client, attr, me.Config.DestinationBucket, me.Config.DestinationPrefix, true)
+	return me.actor(ctx, client, attr, me.Config.DestinationBucket, me.Config.DestinationPrefix, true)
 }
 
 // MoveResult defines all outputs of a move effect.
diff --git a/cmd/cycler/effects/move_test.go b/cmd/cycler/effects/move_test.go
index d8ca123..f711f21 100644
--- a/cmd/cycler/effects/move_test.go
+++ b/cmd/cycler/effects/move_test.go
@@ -13,7 +13,7 @@
 	cycler_pb "go.chromium.org/chromiumos/infra/proto/go/cycler"
 )
 
-func getMoveMock(t *testing.T) MoveEffectActor {
+func getMoveMock(t *testing.T) interface{} {
 	return func(ctx context.Context, client *storage.Client, srcAttr *storage.ObjectAttrs,
 		dstBucket string, prefix string, deleteAfter bool) error {
 		if deleteAfter == false {
diff --git a/cmd/cycler/integration_test/common.sh b/cmd/cycler/integration_test/common.sh
index bacfea3..febff1a 100755
--- a/cmd/cycler/integration_test/common.sh
+++ b/cmd/cycler/integration_test/common.sh
@@ -105,9 +105,11 @@
 # $1 The object bucket name.
 # $2 The logging bucket name.
 # $3 The downloaded logs tmp directory.
+# $4 The stdout tmp file.
+# $5 The json_out tmp file.
 clean_up_test() {
-  if [[ $# -ne 3 ]]; then
-    die 1 "clean_up_test requires 3 arguments"
+  if [[ $# -ne 5 ]]; then
+    die 1 "clean_up_test requires 5 arguments"
   fi
 
   empty_bucket "$1"
@@ -119,6 +121,7 @@
   if [[ "$LEAVELOGS" = false ]]; then
     echo "removing local cycler logs"
     rm -rf "$3"
+    rm "$4" "$5"
   fi
 
   if [[ "$REBUILD" = true ]]; then
@@ -132,6 +135,12 @@
 # Takes the following arguments:
 # $1 The bucket name.
 empty_bucket() {
+  # If the bucket is already empty the subsequent calls to rm will fail with a
+  # command exception: https://github.com/GoogleCloudPlatform/gsutil/issues/417
+  if [[ $(gsutil ls "$1" | wc -l) -eq "0" ]]; then
+    return
+  fi
+
   if [[ $# -ne 1 ]]; then
     die 1 "empty_bucket requires 2 arguments"
   fi
@@ -240,3 +249,80 @@
     false
   fi
 }
+
+# Validate that the size of the bucket is expected.
+#
+# Takes the following arguments:
+# $1 The expected root size in bytes that cycler will report.
+# $2 The json output of cycler.
+validate_size() {
+  expected_rootsizebytes=$1
+  json_out=$2
+  actual_rootsizebytes=$(jq '.PrefixStats.RootSizeBytes' "$json_out")
+  if [[ "$expected_rootsizebytes" -eq "$actual_rootsizebytes" ]]; then
+    true
+  else
+    printf ".PrefixStats.RootSizeByte %s, " "$actual_rootsizebytes"
+    printf "expected %s\n" "$expected_rootsizebytes"
+    false
+  fi
+}
+
+# Validate that the count of the objects is expected.
+#
+# Takes the following arguments:
+# $1 The expected count of objects that cycler will report.
+# $2 The json output of cycler.
+validate_count() {
+  expected_file_count=$1
+  json_out=$2
+  actual_file_count=$(jq '.ActionStats.SizeBytesHistogram.Count' "$json_out")
+  if [[ "$expected_file_count" -eq "$actual_file_count" ]]; then
+    true
+  else
+    printf ".SizeBytesHistogram.Count %s, " "$actual_file_count"
+    printf "expected %s\n" "$expected_file_count"
+    false
+  fi
+}
+
+# Runs common tests against a cycler run returns failure counts.
+#
+# This also has the side effect of decompressing the logs.
+#
+# Takes the following arguments:
+# $1 The expected count of objects that cycler will report.
+# $2 The expected root size in bytes that cycler will report.
+# $3 The logs output of cycler.
+# $4 The json output of cycler.
+common_checks() {
+  local failures=0
+  expected_file_count=$1
+  expected_rootsizebytes=$2
+  logs_out=$3
+  json_out=$4
+
+  # make some assertions on the output gathered.
+  if ! validate_size "$expected_rootsizebytes" "$json_out"; then
+    (( failures++ ))
+  fi
+
+  if ! validate_count "$expected_file_count" "$json_out"; then
+    (( failures++ ))
+  fi
+
+  # decompress logs in place
+  if ! decompress_logs "$logs_out"; then
+    (( failures++ ))
+  fi
+
+  if ! validate_jsonl "$logs_out"; then
+    (( failures++ ))
+  fi
+
+  if ! count_jsonl "$logs_out" "$expected_file_count"; then
+    (( failures++ ))
+  fi
+
+  return "$failures"
+}
diff --git a/cmd/cycler/integration_test/test_chill/run_config.json b/cmd/cycler/integration_test/test_chill/run_config.json
new file mode 100644
index 0000000..70c0b2b
--- /dev/null
+++ b/cmd/cycler/integration_test/test_chill/run_config.json
@@ -0,0 +1,36 @@
+{
+    "run_log_configuration": {
+        "destination_url": "gs://will-be-replaced-by-test/logs",
+        "chunk_size_bytes": 104857600,
+        "channel_size": 10000,
+        "persist_retries": 100,
+        "max_unpersisted_logs": 10
+    },
+
+    "policy_effect_configuration": {
+        "chill": {
+            "to_storage_class": "NEARLINE"
+        },
+        "policy_document_path": "./test_noop/true.rego"
+    },
+
+    "stats_configuration": {
+        "prefix_report_max_depth": 2,
+        "age_days_histogram_options": {
+            "num_buckets": 16,
+            "growth_factor": 1.0,
+            "base_bucket_size": 1.0,
+            "min_value": 0
+        },
+        "size_bytes_histogram_options": {
+            "num_buckets": 16,
+            "growth_factor": 4.0,
+            "base_bucket_size": 1.0,
+            "min_value": 0
+        }
+    },
+
+    "mutation_allowed" : true,
+
+    "bucket": "will-be-replaced-by-test"
+}
diff --git a/cmd/cycler/integration_test/test_chill/test_chill.sh b/cmd/cycler/integration_test/test_chill/test_chill.sh
new file mode 100755
index 0000000..ee8e38b
--- /dev/null
+++ b/cmd/cycler/integration_test/test_chill/test_chill.sh
@@ -0,0 +1,78 @@
+#!/usr/bin/env bash
+
+# Copyright 2020 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+cd "${0%/*}" || (echo "couldn't cd to test directory"; exit 1;)
+TEST_DIR=$PWD
+
+source ../common.sh || (echo "couldn't source common.sh"i; exit 1;)
+
+chill_test () {
+  echo "test: chill effect to nearline"
+
+  failures=0
+
+  test_bucket=$(create_random_bucket)
+  printf "created bucket: %s\n" "$test_bucket"
+
+  log_bucket=$(create_random_bucket)
+  printf "created log bucket: %s\n" "$log_bucket"
+
+  log_url="$log_bucket/logs"
+  logs_out=$(mktemp -d)
+
+  printf "making objects in bucket\n"
+  expected_file_count=10
+  expected_rootsizebytes=2048000
+  create_random_object_in_bucket "$expected_file_count" 3 "$test_bucket" 400
+
+  json_out=$(mktemp)
+  stdout_out=$(mktemp)
+  printf "stdout will be in %s and json will be in %s" "$stdout_out" "$json_out"
+
+  printf "running cycler\n"
+  ./cycler  -bucket "$test_bucket" -iUnderstandCyclerIsInEarlyDevelopment \
+                --runConfigPath "$TEST_DIR/run_config.json" \
+                --jsonOutFile "$json_out" \
+                --runlogURL "$log_url" >"$stdout_out" \
+                --mutationAllowed \
+                || die 1 "cycler run exited non-zero"
+
+  if [[ "$VERBOSE" = true ]]; then
+    echo "cat $json_out" | jq
+  fi
+
+  run_uuid=$(jq -r '.RunUUID' "$json_out")
+  printf "run uuid: %s\n" "$run_uuid"
+
+  # inspect the logging directory, ensure files logged.
+  printf "copying logs locally to: %s\n" "$logs_out"
+  gsutil -m cp -R "$log_bucket/logs/$run_uuid" "$logs_out" >/dev/null 2>&1
+
+  common_checks "$expected_file_count" "$expected_rootsizebytes" "$logs_out" \
+    "$json_out"
+  (( failures += $? ))
+
+  # Test that the chill effect was successful.
+  # Don't trust cycler itself, use gsutil.
+  nl_objs=$(gsutil ls -L "$test_bucket/**" | grep -c "Storage class:.*NEARLINE")
+  if [[ "$nl_objs" -ne "$expected_file_count" ]]; then
+    printf ""
+    (( failures++ ))
+  fi
+
+  # clean up
+  clean_up_test "$test_bucket" "$log_bucket" "$logs_out" "$stdout_out" \
+    "$json_out"
+
+  if [[ $failures -gt 0 ]]; then
+    echo "!!had $failures failures!!"
+    return $failures
+  else
+    return 0
+  fi
+}
+
+chill_test
diff --git a/cmd/cycler/integration_test/test_chill/true.rego b/cmd/cycler/integration_test/test_chill/true.rego
new file mode 100644
index 0000000..efb19f0
--- /dev/null
+++ b/cmd/cycler/integration_test/test_chill/true.rego
@@ -0,0 +1,2 @@
+package cycler
+act := true
\ No newline at end of file
diff --git a/cmd/cycler/integration_test/test_delete/run_config.json b/cmd/cycler/integration_test/test_delete/run_config.json
new file mode 100644
index 0000000..d60175d
--- /dev/null
+++ b/cmd/cycler/integration_test/test_delete/run_config.json
@@ -0,0 +1,34 @@
+{
+    "run_log_configuration": {
+        "destination_url": "gs://will-be-replaced-by-test/logs",
+        "chunk_size_bytes": 104857600,
+        "channel_size": 10000,
+        "persist_retries": 100,
+        "max_unpersisted_logs": 10
+    },
+
+    "policy_effect_configuration": {
+        "delete": {},
+        "policy_document_path": "./test_noop/true.rego"
+    },
+
+    "stats_configuration": {
+        "prefix_report_max_depth": 2,
+        "age_days_histogram_options": {
+            "num_buckets": 16,
+            "growth_factor": 1.0,
+            "base_bucket_size": 1.0,
+            "min_value": 0
+        },
+        "size_bytes_histogram_options": {
+            "num_buckets": 16,
+            "growth_factor": 4.0,
+            "base_bucket_size": 1.0,
+            "min_value": 0
+        }
+    },
+
+    "mutation_allowed" : true,
+
+    "bucket": "will-be-replaced-by-test"
+}
diff --git a/cmd/cycler/integration_test/test_delete/test_delete.sh b/cmd/cycler/integration_test/test_delete/test_delete.sh
new file mode 100755
index 0000000..7914e23
--- /dev/null
+++ b/cmd/cycler/integration_test/test_delete/test_delete.sh
@@ -0,0 +1,78 @@
+#!/usr/bin/env bash
+
+# Copyright 2020 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+cd "${0%/*}" || (echo "couldn't cd to test directory"; exit 1;)
+TEST_DIR=$PWD
+
+source ../common.sh || (echo "couldn't source common.sh"i; exit 1;)
+
+delete_test () {
+  echo "test: delete effect"
+
+  failures=0
+
+  test_bucket=$(create_random_bucket)
+  printf "created bucket: %s\n" "$test_bucket"
+
+  log_bucket=$(create_random_bucket)
+  printf "created log bucket: %s\n" "$log_bucket"
+
+  log_url="$log_bucket/logs"
+  logs_out=$(mktemp -d)
+
+  printf "making objects in bucket\n"
+  expected_file_count=10
+  expected_rootsizebytes=2048000
+  create_random_object_in_bucket "$expected_file_count" 3 "$test_bucket" 400
+
+  json_out=$(mktemp)
+  stdout_out=$(mktemp)
+  printf "stdout will be in %s and json will be in %s" "$stdout_out" "$json_out"
+
+  printf "running cycler\n"
+  ./cycler  -bucket "$test_bucket" -iUnderstandCyclerIsInEarlyDevelopment \
+                --runConfigPath "$TEST_DIR/run_config.json" \
+                --jsonOutFile "$json_out" \
+                --runlogURL "$log_url" >"$stdout_out" \
+                --mutationAllowed \
+                || die 1 "cycler run exited non-zero"
+
+  if [[ "$VERBOSE" = true ]]; then
+    echo "cat $json_out" | jq
+  fi
+
+  run_uuid=$(jq -r '.RunUUID' "$json_out")
+  printf "run uuid: %s\n" "$run_uuid"
+
+  # inspect the logging directory, ensure files logged.
+  printf "copying logs locally to: %s\n" "$logs_out"
+  gsutil -m cp -R "$log_bucket/logs/$run_uuid" "$logs_out" >/dev/null 2>&1
+
+  common_checks "$expected_file_count" "$expected_rootsizebytes" "$logs_out" \
+    "$json_out"
+  (( failures += $? ))
+
+  # Don't trust cycler itself, use gsutil, this actually fails with an expection
+  # somewhat unexpectedly.
+  gsutil ls -L "$test_bucket/**"
+  if [[ "$?" -ne "1" ]]; then
+    printf "The bucket still had objects in it, should have been deleted.\n"
+    (( failures++ ))
+  fi
+
+  # clean up
+  clean_up_test "$test_bucket" "$log_bucket" "$logs_out" "$stdout_out" \
+    "$json_out"
+
+  if [[ $failures -gt 0 ]]; then
+    echo "!!had $failures failures!!"
+    return $failures
+  else
+    return 0
+  fi
+}
+
+delete_test
diff --git a/cmd/cycler/integration_test/test_delete/true.rego b/cmd/cycler/integration_test/test_delete/true.rego
new file mode 100644
index 0000000..efb19f0
--- /dev/null
+++ b/cmd/cycler/integration_test/test_delete/true.rego
@@ -0,0 +1,2 @@
+package cycler
+act := true
\ No newline at end of file
diff --git a/cmd/cycler/integration_test/test_noop/test_noop.sh b/cmd/cycler/integration_test/test_noop/test_noop.sh
index acdb267..9f80019 100755
--- a/cmd/cycler/integration_test/test_noop/test_noop.sh
+++ b/cmd/cycler/integration_test/test_noop/test_noop.sh
@@ -9,7 +9,7 @@
 
 source ../common.sh || (echo "couldn't source common.sh"i; exit 1;)
 
-expected_size_test() {
+noop_test () {
   echo "test: noop policy and basic cycler functionality"
 
   failures=0
@@ -22,6 +22,8 @@
   printf "created bucket: %s\n" "$test_bucket"
 
   printf "making objects in bucket\n"
+  expected_file_count=10
+  expected_rootsizebytes=2048000
   create_random_object_in_bucket 10 3 "$test_bucket" 400
 
   json_out=$(mktemp)
@@ -41,48 +43,24 @@
   run_uuid=$(jq -r '.RunUUID' "$json_out")
   printf "run uuid: %s\n" "$run_uuid"
 
-  # make some assertions on the output gathered.
-  expected_rootsizebytes=2048000
-  actual_rootsizebytes=$(jq '.PrefixStats.RootSizeBytes' "$json_out")
-  if [[ "$expected_rootsizebytes" -ne "$actual_rootsizebytes" ]]; then
-    printf ".PrefixStats.RootSizeByte %s, " "$actual_rootsizebytes\n"
-    printf "expected %s " "$expected_rootsizebytes\n"
-    (( failures++ ))
-  fi
-
-  expected_file_count=10
-  actual_file_count=$(jq '.ActionStats.SizeBytesHistogram.Count' "$json_out")
-  if [[ "$expected_file_count" -ne "$actual_file_count" ]]; then
-    printf ".SizeBytesHistogram.Count %s" "$actual_file_count\n"
-    printf "expected %s" "$expected_file_count\n"
-    (( failures++ ))
-  fi
-
   # inspect the logging directory, ensure files logged.
   printf "copying logs locally to: %s\n" "$logs_out"
   gsutil -m cp -R "$log_bucket/logs/$run_uuid" "$logs_out" >/dev/null 2>&1
 
-  # decompress logs in place
-  if ! decompress_logs "$logs_out"; then
-    (( failures++ ))
-  fi
-
-  if ! validate_jsonl "$logs_out"; then
-    (( failures++ ))
-  fi
-
-  if ! count_jsonl "$logs_out" $expected_file_count; then
-    (( failures++ ))
-  fi
+  common_checks "$expected_file_count" "$expected_rootsizebytes" "$logs_out" \
+    "$json_out"
+  (( failures += $? ))
 
   # clean up
-  clean_up_test "$test_bucket" "$log_bucket" "$logs_out"
+  clean_up_test "$test_bucket" "$log_bucket" "$logs_out" "$stdout_out" \
+    "$json_out"
 
   if [[ $failures -gt 0 ]]; then
+    echo "had $failures failures"
     return $failures
   else
     return 0
   fi
 }
 
-expected_size_test
+noop_test
diff --git a/cmd/cycler/policy.go b/cmd/cycler/policy.go
index 697a68f..1d14127 100644
--- a/cmd/cycler/policy.go
+++ b/cmd/cycler/policy.go
@@ -96,7 +96,6 @@
 	ap.ActionStats.init(ctx, statsConfig)
 
 	var protoConfig interface{}
-	var actor interface{}
 	switch effectType := ap.Config.EffectConfiguration.(type) {
 	case *cycler_pb.PolicyEffectConfiguration_Noop:
 		ap.Effect = &effects.NoopEffect{}
@@ -124,7 +123,7 @@
 		os.Exit(2)
 	}
 
-	actor = ap.Effect.DefaultActor()
+	actor := ap.Effect.DefaultActor()
 	ap.Effect.Initialize(protoConfig, actor, runConfigMutationAllowed, cmdMutationAllowed)
 
 	// Parse the rego expression defined.