xref: /aosp_15_r20/external/bazelbuild-rules_testing/lib/private/target_subject.bzl (revision d605057434dcabba796c020773aab68d9790ff9f)
1*d6050574SRomain Jobredeaux# Copyright 2023 The Bazel Authors. All rights reserved.
2*d6050574SRomain Jobredeaux#
3*d6050574SRomain Jobredeaux# Licensed under the Apache License, Version 2.0 (the "License");
4*d6050574SRomain Jobredeaux# you may not use this file except in compliance with the License.
5*d6050574SRomain Jobredeaux# You may obtain a copy of the License at
6*d6050574SRomain Jobredeaux#
7*d6050574SRomain Jobredeaux#     http://www.apache.org/licenses/LICENSE-2.0
8*d6050574SRomain Jobredeaux#
9*d6050574SRomain Jobredeaux# Unless required by applicable law or agreed to in writing, software
10*d6050574SRomain Jobredeaux# distributed under the License is distributed on an "AS IS" BASIS,
11*d6050574SRomain Jobredeaux# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*d6050574SRomain Jobredeaux# See the License for the specific language governing permissions and
13*d6050574SRomain Jobredeaux# limitations under the License.
14*d6050574SRomain Jobredeaux
15*d6050574SRomain Jobredeaux"""# TargetSubject
16*d6050574SRomain Jobredeaux
17*d6050574SRomain Jobredeaux`TargetSubject` wraps a [`Target`] object and provides method for asserting
18*d6050574SRomain Jobredeauxits state.
19*d6050574SRomain Jobredeaux"""
20*d6050574SRomain Jobredeaux
21*d6050574SRomain Jobredeauxload(
22*d6050574SRomain Jobredeaux    "//lib:util.bzl",
23*d6050574SRomain Jobredeaux    "TestingAspectInfo",
24*d6050574SRomain Jobredeaux)
25*d6050574SRomain Jobredeauxload(":action_subject.bzl", "ActionSubject")
26*d6050574SRomain Jobredeauxload(":bool_subject.bzl", "BoolSubject")
27*d6050574SRomain Jobredeauxload(":collection_subject.bzl", "CollectionSubject")
28*d6050574SRomain Jobredeauxload(":depset_file_subject.bzl", "DepsetFileSubject")
29*d6050574SRomain Jobredeauxload(":execution_info_subject.bzl", "ExecutionInfoSubject")
30*d6050574SRomain Jobredeauxload(":file_subject.bzl", "FileSubject")
31*d6050574SRomain Jobredeauxload(":instrumented_files_info_subject.bzl", "InstrumentedFilesInfoSubject")
32*d6050574SRomain Jobredeauxload(":label_subject.bzl", "LabelSubject")
33*d6050574SRomain Jobredeauxload(":run_environment_info_subject.bzl", "RunEnvironmentInfoSubject")
34*d6050574SRomain Jobredeauxload(":runfiles_subject.bzl", "RunfilesSubject")
35*d6050574SRomain Jobredeauxload(":truth_common.bzl", "enumerate_list_as_lines")
36*d6050574SRomain Jobredeaux
37*d6050574SRomain Jobredeauxdef _target_subject_new(target, meta):
38*d6050574SRomain Jobredeaux    """Creates a subject for asserting Targets.
39*d6050574SRomain Jobredeaux
40*d6050574SRomain Jobredeaux    Method: TargetSubject.new
41*d6050574SRomain Jobredeaux
42*d6050574SRomain Jobredeaux    **Public attributes**:
43*d6050574SRomain Jobredeaux      * `actual`: The wrapped [`Target`] object.
44*d6050574SRomain Jobredeaux
45*d6050574SRomain Jobredeaux    Args:
46*d6050574SRomain Jobredeaux        target: ([`Target`]) the target to check against.
47*d6050574SRomain Jobredeaux        meta: ([`ExpectMeta`]) metadata about the call chain.
48*d6050574SRomain Jobredeaux
49*d6050574SRomain Jobredeaux    Returns:
50*d6050574SRomain Jobredeaux        [`TargetSubject`] object
51*d6050574SRomain Jobredeaux    """
52*d6050574SRomain Jobredeaux    self = struct(target = target, meta = meta)
53*d6050574SRomain Jobredeaux    public = struct(
54*d6050574SRomain Jobredeaux        # keep sorted start
55*d6050574SRomain Jobredeaux        action_generating = lambda *a, **k: _target_subject_action_generating(self, *a, **k),
56*d6050574SRomain Jobredeaux        action_named = lambda *a, **k: _target_subject_action_named(self, *a, **k),
57*d6050574SRomain Jobredeaux        actual = target,
58*d6050574SRomain Jobredeaux        attr = lambda *a, **k: _target_subject_attr(self, *a, **k),
59*d6050574SRomain Jobredeaux        data_runfiles = lambda *a, **k: _target_subject_data_runfiles(self, *a, **k),
60*d6050574SRomain Jobredeaux        default_outputs = lambda *a, **k: _target_subject_default_outputs(self, *a, **k),
61*d6050574SRomain Jobredeaux        executable = lambda *a, **k: _target_subject_executable(self, *a, **k),
62*d6050574SRomain Jobredeaux        failures = lambda *a, **k: _target_subject_failures(self, *a, **k),
63*d6050574SRomain Jobredeaux        has_provider = lambda *a, **k: _target_subject_has_provider(self, *a, **k),
64*d6050574SRomain Jobredeaux        label = lambda *a, **k: _target_subject_label(self, *a, **k),
65*d6050574SRomain Jobredeaux        meta = meta,
66*d6050574SRomain Jobredeaux        output_group = lambda *a, **k: _target_subject_output_group(self, *a, **k),
67*d6050574SRomain Jobredeaux        provider = lambda *a, **k: _target_subject_provider(self, *a, **k),
68*d6050574SRomain Jobredeaux        runfiles = lambda *a, **k: _target_subject_runfiles(self, *a, **k),
69*d6050574SRomain Jobredeaux        tags = lambda *a, **k: _target_subject_tags(self, *a, **k),
70*d6050574SRomain Jobredeaux        # keep sorted end
71*d6050574SRomain Jobredeaux    )
72*d6050574SRomain Jobredeaux    return public
73*d6050574SRomain Jobredeaux
74*d6050574SRomain Jobredeauxdef _target_subject_runfiles(self):
75*d6050574SRomain Jobredeaux    """Creates a subject asserting on the target's default runfiles.
76*d6050574SRomain Jobredeaux
77*d6050574SRomain Jobredeaux    Method: TargetSubject.runfiles
78*d6050574SRomain Jobredeaux
79*d6050574SRomain Jobredeaux    Args:
80*d6050574SRomain Jobredeaux        self: implicitly added.
81*d6050574SRomain Jobredeaux
82*d6050574SRomain Jobredeaux    Returns:
83*d6050574SRomain Jobredeaux        [`RunfilesSubject`] object.
84*d6050574SRomain Jobredeaux    """
85*d6050574SRomain Jobredeaux    meta = self.meta.derive("runfiles()")
86*d6050574SRomain Jobredeaux    return RunfilesSubject.new(self.target[DefaultInfo].default_runfiles, meta, "default")
87*d6050574SRomain Jobredeaux
88*d6050574SRomain Jobredeauxdef _target_subject_tags(self):
89*d6050574SRomain Jobredeaux    """Gets the target's tags as a `CollectionSubject`
90*d6050574SRomain Jobredeaux
91*d6050574SRomain Jobredeaux    Method: TargetSubject.tags
92*d6050574SRomain Jobredeaux
93*d6050574SRomain Jobredeaux    Args:
94*d6050574SRomain Jobredeaux        self: implicitly added
95*d6050574SRomain Jobredeaux
96*d6050574SRomain Jobredeaux    Returns:
97*d6050574SRomain Jobredeaux        [`CollectionSubject`] asserting the target's tags.
98*d6050574SRomain Jobredeaux    """
99*d6050574SRomain Jobredeaux    return CollectionSubject.new(
100*d6050574SRomain Jobredeaux        _target_subject_get_attr(self, "tags"),
101*d6050574SRomain Jobredeaux        self.meta.derive("tags()"),
102*d6050574SRomain Jobredeaux    )
103*d6050574SRomain Jobredeaux
104*d6050574SRomain Jobredeauxdef _target_subject_get_attr(self, name):
105*d6050574SRomain Jobredeaux    if TestingAspectInfo not in self.target:
106*d6050574SRomain Jobredeaux        fail("TestingAspectInfo provider missing: if this is a second order or higher " +
107*d6050574SRomain Jobredeaux             "dependency, the recursing testing aspect must be enabled.")
108*d6050574SRomain Jobredeaux
109*d6050574SRomain Jobredeaux    attrs = self.target[TestingAspectInfo].attrs
110*d6050574SRomain Jobredeaux    if not hasattr(attrs, name):
111*d6050574SRomain Jobredeaux        fail("Attr '{}' not present for target {}".format(name, self.target.label))
112*d6050574SRomain Jobredeaux    else:
113*d6050574SRomain Jobredeaux        return getattr(attrs, name)
114*d6050574SRomain Jobredeaux
115*d6050574SRomain Jobredeauxdef _target_subject_data_runfiles(self):
116*d6050574SRomain Jobredeaux    """Creates a subject asserting on the target's data runfiles.
117*d6050574SRomain Jobredeaux
118*d6050574SRomain Jobredeaux    Method: TargetSubject.data_runfiles
119*d6050574SRomain Jobredeaux
120*d6050574SRomain Jobredeaux    Args:
121*d6050574SRomain Jobredeaux        self: implicitly added.
122*d6050574SRomain Jobredeaux
123*d6050574SRomain Jobredeaux    Returns:
124*d6050574SRomain Jobredeaux        [`RunfilesSubject`] object
125*d6050574SRomain Jobredeaux    """
126*d6050574SRomain Jobredeaux    meta = self.meta.derive("data_runfiles()")
127*d6050574SRomain Jobredeaux    return RunfilesSubject.new(self.target[DefaultInfo].data_runfiles, meta, "data")
128*d6050574SRomain Jobredeaux
129*d6050574SRomain Jobredeauxdef _target_subject_default_outputs(self):
130*d6050574SRomain Jobredeaux    """Creates a subject asserting on the target's default outputs.
131*d6050574SRomain Jobredeaux
132*d6050574SRomain Jobredeaux    Method: TargetSubject.default_outputs
133*d6050574SRomain Jobredeaux
134*d6050574SRomain Jobredeaux    Args:
135*d6050574SRomain Jobredeaux        self: implicitly added.
136*d6050574SRomain Jobredeaux
137*d6050574SRomain Jobredeaux    Returns:
138*d6050574SRomain Jobredeaux        [`DepsetFileSubject`] object.
139*d6050574SRomain Jobredeaux    """
140*d6050574SRomain Jobredeaux    meta = self.meta.derive("default_outputs()")
141*d6050574SRomain Jobredeaux    return DepsetFileSubject.new(self.target[DefaultInfo].files, meta)
142*d6050574SRomain Jobredeaux
143*d6050574SRomain Jobredeauxdef _target_subject_executable(self):
144*d6050574SRomain Jobredeaux    """Creates a subject asesrting on the target's executable File.
145*d6050574SRomain Jobredeaux
146*d6050574SRomain Jobredeaux    Method: TargetSubject.executable
147*d6050574SRomain Jobredeaux
148*d6050574SRomain Jobredeaux    Args:
149*d6050574SRomain Jobredeaux        self: implicitly added.
150*d6050574SRomain Jobredeaux
151*d6050574SRomain Jobredeaux    Returns:
152*d6050574SRomain Jobredeaux        [`FileSubject`] object.
153*d6050574SRomain Jobredeaux    """
154*d6050574SRomain Jobredeaux    meta = self.meta.derive("executable()")
155*d6050574SRomain Jobredeaux    return FileSubject.new(self.target[DefaultInfo].files_to_run.executable, meta)
156*d6050574SRomain Jobredeaux
157*d6050574SRomain Jobredeauxdef _target_subject_failures(self):
158*d6050574SRomain Jobredeaux    """Creates a subject asserting on the target's failure message strings.
159*d6050574SRomain Jobredeaux
160*d6050574SRomain Jobredeaux    Method: TargetSubject.failures
161*d6050574SRomain Jobredeaux
162*d6050574SRomain Jobredeaux    Args:
163*d6050574SRomain Jobredeaux        self: implicitly added
164*d6050574SRomain Jobredeaux
165*d6050574SRomain Jobredeaux    Returns:
166*d6050574SRomain Jobredeaux        [`CollectionSubject`] of [`str`].
167*d6050574SRomain Jobredeaux    """
168*d6050574SRomain Jobredeaux    meta = self.meta.derive("failures()")
169*d6050574SRomain Jobredeaux    if AnalysisFailureInfo in self.target:
170*d6050574SRomain Jobredeaux        failure_messages = sorted([
171*d6050574SRomain Jobredeaux            f.message
172*d6050574SRomain Jobredeaux            for f in self.target[AnalysisFailureInfo].causes.to_list()
173*d6050574SRomain Jobredeaux        ])
174*d6050574SRomain Jobredeaux    else:
175*d6050574SRomain Jobredeaux        failure_messages = []
176*d6050574SRomain Jobredeaux    return CollectionSubject.new(failure_messages, meta, container_name = "failure messages")
177*d6050574SRomain Jobredeaux
178*d6050574SRomain Jobredeauxdef _target_subject_has_provider(self, provider):
179*d6050574SRomain Jobredeaux    """Asserts that the target as provider `provider`.
180*d6050574SRomain Jobredeaux
181*d6050574SRomain Jobredeaux    Method: TargetSubject.has_provider
182*d6050574SRomain Jobredeaux
183*d6050574SRomain Jobredeaux    Args:
184*d6050574SRomain Jobredeaux        self: implicitly added.
185*d6050574SRomain Jobredeaux        provider: The provider object to check for.
186*d6050574SRomain Jobredeaux    """
187*d6050574SRomain Jobredeaux    if self.meta.has_provider(self.target, provider):
188*d6050574SRomain Jobredeaux        return
189*d6050574SRomain Jobredeaux    self.meta.add_failure(
190*d6050574SRomain Jobredeaux        "expected to have provider: {}".format(_provider_name(provider)),
191*d6050574SRomain Jobredeaux        "but provider was not found",
192*d6050574SRomain Jobredeaux    )
193*d6050574SRomain Jobredeaux
194*d6050574SRomain Jobredeauxdef _target_subject_label(self):
195*d6050574SRomain Jobredeaux    """Returns a `LabelSubject` for the target's label value.
196*d6050574SRomain Jobredeaux
197*d6050574SRomain Jobredeaux    Method: TargetSubject.label
198*d6050574SRomain Jobredeaux    """
199*d6050574SRomain Jobredeaux    return LabelSubject.new(
200*d6050574SRomain Jobredeaux        label = self.target.label,
201*d6050574SRomain Jobredeaux        meta = self.meta.derive(expr = "label()"),
202*d6050574SRomain Jobredeaux    )
203*d6050574SRomain Jobredeaux
204*d6050574SRomain Jobredeauxdef _target_subject_output_group(self, name):
205*d6050574SRomain Jobredeaux    """Returns a DepsetFileSubject of the files in the named output group.
206*d6050574SRomain Jobredeaux
207*d6050574SRomain Jobredeaux    Method: TargetSubject.output_group
208*d6050574SRomain Jobredeaux
209*d6050574SRomain Jobredeaux    Args:
210*d6050574SRomain Jobredeaux        self: implicitly added.
211*d6050574SRomain Jobredeaux        name: ([`str`]) an output group name. If it isn't present, an error is raised.
212*d6050574SRomain Jobredeaux
213*d6050574SRomain Jobredeaux    Returns:
214*d6050574SRomain Jobredeaux        DepsetFileSubject of the named output group.
215*d6050574SRomain Jobredeaux    """
216*d6050574SRomain Jobredeaux    info = self.target[OutputGroupInfo]
217*d6050574SRomain Jobredeaux    if not hasattr(info, name):
218*d6050574SRomain Jobredeaux        fail("OutputGroupInfo.{} not present for target {}".format(name, self.target.label))
219*d6050574SRomain Jobredeaux    return DepsetFileSubject.new(
220*d6050574SRomain Jobredeaux        getattr(info, name),
221*d6050574SRomain Jobredeaux        meta = self.meta.derive("output_group({})".format(name)),
222*d6050574SRomain Jobredeaux    )
223*d6050574SRomain Jobredeaux
224*d6050574SRomain Jobredeauxdef _target_subject_provider(self, provider_key, factory = None):
225*d6050574SRomain Jobredeaux    """Returns a subject for a provider in the target.
226*d6050574SRomain Jobredeaux
227*d6050574SRomain Jobredeaux    Method: TargetSubject.provider
228*d6050574SRomain Jobredeaux
229*d6050574SRomain Jobredeaux    Args:
230*d6050574SRomain Jobredeaux        self: implicitly added.
231*d6050574SRomain Jobredeaux        provider_key: The provider key to create a subject for
232*d6050574SRomain Jobredeaux        factory: optional callable. The factory function to use to create
233*d6050574SRomain Jobredeaux            the subject for the found provider. Required if the provider key is
234*d6050574SRomain Jobredeaux            not an inherently supported provider. It must have the following
235*d6050574SRomain Jobredeaux            signature: `def factory(value, /, *, meta)`.
236*d6050574SRomain Jobredeaux
237*d6050574SRomain Jobredeaux    Returns:
238*d6050574SRomain Jobredeaux        A subject wrapper of the provider value.
239*d6050574SRomain Jobredeaux    """
240*d6050574SRomain Jobredeaux    if not factory:
241*d6050574SRomain Jobredeaux        for key, value in _PROVIDER_SUBJECT_FACTORIES:
242*d6050574SRomain Jobredeaux            if key == provider_key:
243*d6050574SRomain Jobredeaux                factory = value
244*d6050574SRomain Jobredeaux                break
245*d6050574SRomain Jobredeaux
246*d6050574SRomain Jobredeaux    if not factory:
247*d6050574SRomain Jobredeaux        fail("Unsupported provider: {}".format(provider_key))
248*d6050574SRomain Jobredeaux    info = self.target[provider_key]
249*d6050574SRomain Jobredeaux
250*d6050574SRomain Jobredeaux    return factory(
251*d6050574SRomain Jobredeaux        info,
252*d6050574SRomain Jobredeaux        meta = self.meta.derive("provider({})".format(provider_key)),
253*d6050574SRomain Jobredeaux    )
254*d6050574SRomain Jobredeaux
255*d6050574SRomain Jobredeauxdef _target_subject_action_generating(self, short_path):
256*d6050574SRomain Jobredeaux    """Get the single action generating the given path.
257*d6050574SRomain Jobredeaux
258*d6050574SRomain Jobredeaux    Method: TargetSubject.action_generating
259*d6050574SRomain Jobredeaux
260*d6050574SRomain Jobredeaux    NOTE: in order to use this method, the target must have the `TestingAspectInfo`
261*d6050574SRomain Jobredeaux    provider (added by the `testing_aspect` aspect.)
262*d6050574SRomain Jobredeaux
263*d6050574SRomain Jobredeaux    Args:
264*d6050574SRomain Jobredeaux        self: implicitly added.
265*d6050574SRomain Jobredeaux        short_path: ([`str`]) the output's short_path to match. The value is
266*d6050574SRomain Jobredeaux            formatted using [`format_str`], so its template keywords can be
267*d6050574SRomain Jobredeaux            directly passed.
268*d6050574SRomain Jobredeaux
269*d6050574SRomain Jobredeaux    Returns:
270*d6050574SRomain Jobredeaux        [`ActionSubject`] for the matching action. If no action is found, or
271*d6050574SRomain Jobredeaux        more than one action matches, then an error is raised.
272*d6050574SRomain Jobredeaux    """
273*d6050574SRomain Jobredeaux
274*d6050574SRomain Jobredeaux    if not self.meta.has_provider(self.target, TestingAspectInfo):
275*d6050574SRomain Jobredeaux        fail("TestingAspectInfo provider missing: if this is a second order or higher " +
276*d6050574SRomain Jobredeaux             "dependency, the recursing testing aspect must be enabled.")
277*d6050574SRomain Jobredeaux
278*d6050574SRomain Jobredeaux    short_path = self.meta.format_str(short_path)
279*d6050574SRomain Jobredeaux    actions = []
280*d6050574SRomain Jobredeaux    for action in self.meta.get_provider(self.target, TestingAspectInfo).actions:
281*d6050574SRomain Jobredeaux        for output in action.outputs.to_list():
282*d6050574SRomain Jobredeaux            if output.short_path == short_path:
283*d6050574SRomain Jobredeaux                actions.append(action)
284*d6050574SRomain Jobredeaux                break
285*d6050574SRomain Jobredeaux    if not actions:
286*d6050574SRomain Jobredeaux        fail("No action generating '{}'".format(short_path))
287*d6050574SRomain Jobredeaux    elif len(actions) > 1:
288*d6050574SRomain Jobredeaux        fail("Expected 1 action to generate '{output}', found {count}: {actions}".format(
289*d6050574SRomain Jobredeaux            output = short_path,
290*d6050574SRomain Jobredeaux            count = len(actions),
291*d6050574SRomain Jobredeaux            actions = "\n".join([str(a) for a in actions]),
292*d6050574SRomain Jobredeaux        ))
293*d6050574SRomain Jobredeaux    action = actions[0]
294*d6050574SRomain Jobredeaux    meta = self.meta.derive(
295*d6050574SRomain Jobredeaux        expr = "action_generating({})".format(short_path),
296*d6050574SRomain Jobredeaux        details = ["action: [{}] {}".format(action.mnemonic, action)],
297*d6050574SRomain Jobredeaux    )
298*d6050574SRomain Jobredeaux    return ActionSubject.new(action, meta)
299*d6050574SRomain Jobredeaux
300*d6050574SRomain Jobredeauxdef _target_subject_action_named(self, mnemonic):
301*d6050574SRomain Jobredeaux    """Get the single action with the matching mnemonic.
302*d6050574SRomain Jobredeaux
303*d6050574SRomain Jobredeaux    Method: TargetSubject.action_named
304*d6050574SRomain Jobredeaux
305*d6050574SRomain Jobredeaux    NOTE: in order to use this method, the target must have the [`TestingAspectInfo`]
306*d6050574SRomain Jobredeaux    provider (added by the [`testing_aspect`] aspect.)
307*d6050574SRomain Jobredeaux
308*d6050574SRomain Jobredeaux    Args:
309*d6050574SRomain Jobredeaux        self: implicitly added.
310*d6050574SRomain Jobredeaux        mnemonic: ([`str`]) the mnemonic to match
311*d6050574SRomain Jobredeaux
312*d6050574SRomain Jobredeaux    Returns:
313*d6050574SRomain Jobredeaux        [`ActionSubject`]. If no action matches, or more than one action matches, an error
314*d6050574SRomain Jobredeaux        is raised.
315*d6050574SRomain Jobredeaux    """
316*d6050574SRomain Jobredeaux    if TestingAspectInfo not in self.target:
317*d6050574SRomain Jobredeaux        fail("TestingAspectInfo provider missing: if this is a second order or higher " +
318*d6050574SRomain Jobredeaux             "dependency, the recursing testing aspect must be enabled.")
319*d6050574SRomain Jobredeaux    actions = [a for a in self.target[TestingAspectInfo].actions if a.mnemonic == mnemonic]
320*d6050574SRomain Jobredeaux    if not actions:
321*d6050574SRomain Jobredeaux        fail(
322*d6050574SRomain Jobredeaux            "No action named '{name}' for target {target}.\nFound: {found}".format(
323*d6050574SRomain Jobredeaux                name = mnemonic,
324*d6050574SRomain Jobredeaux                target = self.target.label,
325*d6050574SRomain Jobredeaux                found = enumerate_list_as_lines([
326*d6050574SRomain Jobredeaux                    a.mnemonic
327*d6050574SRomain Jobredeaux                    for a in self.target[TestingAspectInfo].actions
328*d6050574SRomain Jobredeaux                ]),
329*d6050574SRomain Jobredeaux            ),
330*d6050574SRomain Jobredeaux        )
331*d6050574SRomain Jobredeaux    elif len(actions) > 1:
332*d6050574SRomain Jobredeaux        fail("Expected 1 action to match '{name}', found {count}: {actions}".format(
333*d6050574SRomain Jobredeaux            name = mnemonic,
334*d6050574SRomain Jobredeaux            count = len(actions),
335*d6050574SRomain Jobredeaux            actions = "\n".join([str(a) for a in actions]),
336*d6050574SRomain Jobredeaux        ))
337*d6050574SRomain Jobredeaux    action = actions[0]
338*d6050574SRomain Jobredeaux    meta = self.meta.derive(
339*d6050574SRomain Jobredeaux        expr = "action_named({})".format(mnemonic),
340*d6050574SRomain Jobredeaux        details = ["action: [{}] {}".format(action.mnemonic, action)],
341*d6050574SRomain Jobredeaux    )
342*d6050574SRomain Jobredeaux    return ActionSubject.new(action, meta)
343*d6050574SRomain Jobredeaux
344*d6050574SRomain Jobredeaux# NOTE: This map should only have attributes that are common to all target
345*d6050574SRomain Jobredeaux# types, otherwise we can't rely on an attribute having a specific type.
346*d6050574SRomain Jobredeaux_ATTR_NAME_TO_SUBJECT_FACTORY = {
347*d6050574SRomain Jobredeaux    "testonly": BoolSubject.new,
348*d6050574SRomain Jobredeaux}
349*d6050574SRomain Jobredeaux
350*d6050574SRomain Jobredeauxdef _target_subject_attr(self, name, *, factory = None):
351*d6050574SRomain Jobredeaux    """Gets a subject-wrapped value for the named attribute.
352*d6050574SRomain Jobredeaux
353*d6050574SRomain Jobredeaux    Method: TargetSubject.attr
354*d6050574SRomain Jobredeaux
355*d6050574SRomain Jobredeaux    NOTE: in order to use this method, the target must have the `TestingAspectInfo`
356*d6050574SRomain Jobredeaux    provider (added by the `testing_aspect` aspect.)
357*d6050574SRomain Jobredeaux
358*d6050574SRomain Jobredeaux    Args:
359*d6050574SRomain Jobredeaux        self: implicitly added
360*d6050574SRomain Jobredeaux        name: ([`str`]) the attribute to get. If it's an unsupported attribute, and
361*d6050574SRomain Jobredeaux            no explicit factory was provided, an error will be raised.
362*d6050574SRomain Jobredeaux        factory: (callable) function to create the returned subject based on
363*d6050574SRomain Jobredeaux            the attribute value. If specified, it takes precedence over the
364*d6050574SRomain Jobredeaux            attributes that are inherently understood. It must have the
365*d6050574SRomain Jobredeaux            following signature: `def factory(value, *, meta)`, where `value` is
366*d6050574SRomain Jobredeaux            the value of the attribute, and `meta` is the call chain metadata.
367*d6050574SRomain Jobredeaux
368*d6050574SRomain Jobredeaux    Returns:
369*d6050574SRomain Jobredeaux        A Subject-like object for the given attribute. The particular subject
370*d6050574SRomain Jobredeaux        type returned depends on attribute and `factory` arg. If it isn't know
371*d6050574SRomain Jobredeaux        what type of subject to use for the attribute, an error is raised.
372*d6050574SRomain Jobredeaux    """
373*d6050574SRomain Jobredeaux    if TestingAspectInfo not in self.target:
374*d6050574SRomain Jobredeaux        fail("TestingAspectInfo provider missing: if this is a second order or higher " +
375*d6050574SRomain Jobredeaux             "dependency, the recursing testing aspect must be enabled.")
376*d6050574SRomain Jobredeaux
377*d6050574SRomain Jobredeaux    attr_value = getattr(self.target[TestingAspectInfo].attrs, name)
378*d6050574SRomain Jobredeaux    if not factory:
379*d6050574SRomain Jobredeaux        if name not in _ATTR_NAME_TO_SUBJECT_FACTORY:
380*d6050574SRomain Jobredeaux            fail("Unsupported attr: {}".format(name))
381*d6050574SRomain Jobredeaux        factory = _ATTR_NAME_TO_SUBJECT_FACTORY[name]
382*d6050574SRomain Jobredeaux
383*d6050574SRomain Jobredeaux    return factory(
384*d6050574SRomain Jobredeaux        attr_value,
385*d6050574SRomain Jobredeaux        meta = self.meta.derive("attr({})".format(name)),
386*d6050574SRomain Jobredeaux    )
387*d6050574SRomain Jobredeaux
388*d6050574SRomain Jobredeaux# Providers aren't hashable, so we have to use a list of (key, value)
389*d6050574SRomain Jobredeaux_PROVIDER_SUBJECT_FACTORIES = [
390*d6050574SRomain Jobredeaux    (InstrumentedFilesInfo, InstrumentedFilesInfoSubject.new),
391*d6050574SRomain Jobredeaux    (RunEnvironmentInfo, RunEnvironmentInfoSubject.new),
392*d6050574SRomain Jobredeaux    (testing.ExecutionInfo, ExecutionInfoSubject.new),
393*d6050574SRomain Jobredeaux]
394*d6050574SRomain Jobredeaux
395*d6050574SRomain Jobredeauxdef _provider_name(provider):
396*d6050574SRomain Jobredeaux    # This relies on implementation details of how Starlark represents
397*d6050574SRomain Jobredeaux    # providers, and isn't entirely accurate, but works well enough
398*d6050574SRomain Jobredeaux    # for error messages.
399*d6050574SRomain Jobredeaux    return str(provider).split("<function ")[1].split(">")[0]
400*d6050574SRomain Jobredeaux
401*d6050574SRomain Jobredeaux# We use this name so it shows up nice in docs.
402*d6050574SRomain Jobredeaux# buildifier: disable=name-conventions
403*d6050574SRomain JobredeauxTargetSubject = struct(
404*d6050574SRomain Jobredeaux    new = _target_subject_new,
405*d6050574SRomain Jobredeaux    runfiles = _target_subject_runfiles,
406*d6050574SRomain Jobredeaux    tags = _target_subject_tags,
407*d6050574SRomain Jobredeaux    get_attr = _target_subject_get_attr,
408*d6050574SRomain Jobredeaux    data_runfiles = _target_subject_data_runfiles,
409*d6050574SRomain Jobredeaux    default_outputs = _target_subject_default_outputs,
410*d6050574SRomain Jobredeaux    executable = _target_subject_executable,
411*d6050574SRomain Jobredeaux    failures = _target_subject_failures,
412*d6050574SRomain Jobredeaux    has_provider = _target_subject_has_provider,
413*d6050574SRomain Jobredeaux    label = _target_subject_label,
414*d6050574SRomain Jobredeaux    output_group = _target_subject_output_group,
415*d6050574SRomain Jobredeaux    provider = _target_subject_provider,
416*d6050574SRomain Jobredeaux    action_generating = _target_subject_action_generating,
417*d6050574SRomain Jobredeaux    action_named = _target_subject_action_named,
418*d6050574SRomain Jobredeaux    attr = _target_subject_attr,
419*d6050574SRomain Jobredeaux)
420