analysistest API for retrieval of actions registered by target under test


diff --git a/lib/BUILD b/lib/BUILD
index ee961d5..dd56187 100644
--- a/lib/BUILD
+++ b/lib/BUILD
@@ -70,6 +70,7 @@
     deps = [
         ":new_sets",
         ":sets",
+        ":types",
     ],
 )
 
diff --git a/lib/unittest.bzl b/lib/unittest.bzl
index 793955f..de81a4d 100644
--- a/lib/unittest.bzl
+++ b/lib/unittest.bzl
@@ -21,6 +21,7 @@
 
 load(":new_sets.bzl", new_sets = "sets")
 load(":sets.bzl", "sets")
+load(":types.bzl", "types")
 
 # The following function should only be called from WORKSPACE files and workspace macros.
 def register_unittest_toolchains():
@@ -119,6 +120,16 @@
         toolchains = [TOOLCHAIN_TYPE],
     )
 
+_ActionInfo = provider(fields = ["actions"])
+
+def _action_retrieving_aspect_impl(target, ctx):
+    return [_ActionInfo(actions = target.actions)]
+
+_action_retrieving_aspect = aspect(
+    attr_aspects = [],
+    implementation = _action_retrieving_aspect_impl,
+)
+
 # TODO(cparsons): Provide more full documentation on analysis testing in README.
 def _make_analysis_test(impl, expect_failure = False, config_settings = {}):
     """Creates an analysis test rule from its implementation function.
@@ -170,9 +181,16 @@
         test_transition = analysis_test_transition(
             settings = changed_settings,
         )
-        attrs["target_under_test"] = attr.label(cfg = test_transition, mandatory = True)
+        attrs["target_under_test"] = attr.label(
+            aspects = [_action_retrieving_aspect],
+            cfg = test_transition,
+            mandatory = True,
+        )
     else:
-        attrs["target_under_test"] = attr.label(mandatory = True)
+        attrs["target_under_test"] = attr.label(
+            aspects = [_action_retrieving_aspect],
+            mandatory = True,
+        )
 
     return rule(
         impl,
@@ -396,10 +414,10 @@
     `expect_failures = True` is specified.
 
     Args:
-      env: The test environment returned by `unittest.begin`.
+      env: The test environment returned by `analysistest.begin`.
       expected_failure_msg: The error message to expect as a result of analysis failures.
     """
-    dep = getattr(env.ctx.attr, "target_under_test")[0]
+    dep = _target_under_test(env)
     if AnalysisFailureInfo in dep:
         dep_failure = dep[AnalysisFailureInfo]
         actual_errors = ""
@@ -412,6 +430,31 @@
     else:
         _fail(env, "Expected failure of target_under_test, but found success")
 
+def _target_actions(env):
+    """Returns a list of actions registered by the target under test.
+
+    Args:
+      env: The test environment returned by `analysistest.begin`.
+    """
+
+    # Validate?
+    dep = _target_under_test(env)
+    return dep[_ActionInfo].actions
+
+def _target_under_test(env):
+    """Returns the target under test.
+
+    Args:
+      env: The test environment returned by `analysistest.begin`.
+    """
+    result = getattr(env.ctx.attr, "target_under_test")
+    if types.is_list(result):
+        if result:
+            return result[0]
+        else:
+            fail("test rule does not have a target_under_test")
+    return result
+
 asserts = struct(
     expect_failure = _expect_failure,
     equals = _assert_equals,
@@ -434,4 +477,6 @@
     begin = _begin,
     end = _end_analysis_test,
     fail = _fail,
+    target_actions = _target_actions,
+    target_under_test = _target_under_test,
 )
diff --git a/tests/unittest_tests.bzl b/tests/unittest_tests.bzl
index 9bdecc6..20fd890 100644
--- a/tests/unittest_tests.bzl
+++ b/tests/unittest_tests.bzl
@@ -35,7 +35,7 @@
     """Test to verify that an analysis test may change configuration."""
     env = analysistest.begin(ctx)
 
-    dep_min_os_version = ctx.attr.target_under_test[0][_ChangeSettingInfo].min_os_version
+    dep_min_os_version = analysistest.target_under_test(env)[_ChangeSettingInfo].min_os_version
     asserts.equals(env, "1234.5678", dep_min_os_version)
 
     return analysistest.end(env)
@@ -132,6 +132,35 @@
     },
 )
 
+####################################
+####### inspect_actions_test #######
+####################################
+def _inspect_actions_test(ctx):
+    """Test verifying actions registered by a target."""
+    env = analysistest.begin(ctx)
+
+    actions = analysistest.target_actions(env)
+    asserts.equals(env, 1, len(actions))
+    action_output = actions[0].outputs.to_list()[0]
+    asserts.equals(env, "out.txt", action_output.basename)
+    return analysistest.end(env)
+
+def _inspect_actions_fake_rule(ctx):
+    out_file = ctx.actions.declare_file("out.txt")
+    ctx.actions.run_shell(
+        command = "echo 'hello' > %s" % out_file.basename,
+        outputs = [out_file],
+    )
+    return [DefaultInfo(files = depset([out_file]))]
+
+inspect_actions_fake_rule = rule(
+    implementation = _inspect_actions_fake_rule,
+)
+
+inspect_actions_test = analysistest.make(
+    _inspect_actions_test,
+)
+
 #########################################
 
 def unittest_passing_tests_suite():
@@ -171,3 +200,12 @@
         name = "change_setting_with_failure_fake_target",
         tags = ["manual"],
     )
+
+    inspect_actions_test(
+        name = "inspect_actions_test",
+        target_under_test = ":inspect_actions_fake_target",
+    )
+    inspect_actions_fake_rule(
+        name = "inspect_actions_fake_target",
+        tags = ["manual"],
+    )