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