xref: /aosp_15_r20/external/bazelbuild-rules_testing/tests/truth_tests.bzl (revision d605057434dcabba796c020773aab68d9790ff9f)
1# Copyright 2023 The Bazel Authors. All rights reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""Tests for truth.bzl."""
16
17load("@bazel_skylib//lib:unittest.bzl", ut_asserts = "asserts")
18load("//lib:truth.bzl", "matching", "subjects", "truth")
19load("//lib:analysis_test.bzl", "analysis_test", "test_suite")
20
21# Bazel 5 has a bug where every access of testing.ExecutionInfo is a new
22# object that isn't equal to itself. This is fixed in Bazel 6.
23_IS_BAZEL_6_OR_HIGHER = (testing.ExecutionInfo == testing.ExecutionInfo)
24
25_suite = []
26
27def _fake_env(env):
28    failures = []
29    env1 = struct(
30        ctx = env.ctx,
31        failures = failures,
32        fail = lambda msg: failures.append(msg),  # Silent fail
33    )
34    env2 = struct(
35        ctx = env.ctx,
36        failures = failures,
37        fail = lambda msg: failures.append(msg),  # Silent fail
38        expect = truth.expect(env1),
39        reset = lambda: failures.clear(),
40    )
41    return env2
42
43def _end(env, fake_env):
44    _guard_against_stray_failures(env = env, fake_env = fake_env)
45
46def _guard_against_stray_failures(*, env, fake_env):
47    ut_asserts.true(
48        env,
49        len(fake_env.failures) == 0,
50        "failures remain: clear after each expected failure\n{}".format(
51            "\n".join(fake_env.failures),
52        ),
53    )
54
55def action_subject_test(name):
56    analysis_test(name, impl = _action_subject_test, target = "truth_tests_helper")
57
58def _action_subject_test(env, target):
59    fake_env = _fake_env(env)
60    subject = fake_env.expect.that_target(
61        target,
62    ).action_named("Action1")
63
64    subject.contains_flag_values([
65        ("--arg1flag", "arg1value"),
66        ("--arg2flag", "arg2value"),
67    ])
68    _assert_no_failures(
69        fake_env,
70        env = env,
71        msg = "check contains_flag_values success",
72    )
73
74    subject.contains_flag_values([
75        ("--missingflag", "whatever"),
76        ("--arg1flag", "wrongvalue"),
77    ])
78    _assert_failure(
79        fake_env,
80        [
81            "2 expected flags with values missing from argv",
82            "0: '--arg1flag' with value 'wrongvalue'",
83            "1: '--missingflag' (not specified)",
84            "actual argv",
85            "1: arg1",
86            "2: --boolflag",
87            "3: --arg1flag",
88            "4: arg1value",
89            "5: --arg2flag=arg2value",
90        ],
91        env = env,
92        msg = "check contains_flag_values failure",
93    )
94
95    subject.contains_none_of_flag_values([
96        ("--doesnotexist", "whatever"),
97        ("--arg1flag", "differentvalue"),
98    ])
99    _assert_no_failures(
100        fake_env,
101        env = env,
102        msg = "check contains_none_of_flag_values success",
103    )
104
105    subject.contains_none_of_flag_values([
106        ("--arg1flag", "arg1value"),
107    ])
108    _assert_failure(
109        fake_env,
110        [
111            ("expected not to contain any of: \n" +  # note space after colon
112             "  0: '--arg1flag' with value 'arg1value'\n"),
113            ("but 1 found:\n" +
114             "  0: '--arg1flag' with value 'arg1value'\n"),
115            "actual values:\n",
116            # Element 0 of actual is omitted because it has build-config
117            # specific values within it.
118            ("  1: arg1\n" +
119             "  2: --boolflag\n" +
120             "  3: --arg1flag\n" +
121             "  4: arg1value\n" +
122             "  5: --arg2flag=arg2value\n"),
123        ],
124        env = env,
125        msg = "check contains_none_of_flag_values failure",
126    )
127    _end(env, fake_env)
128
129_suite.append(action_subject_test)
130
131def bool_subject_test(name):
132    analysis_test(name, impl = _bool_subject_test, target = "truth_tests_helper")
133
134def _bool_subject_test(env, _target):
135    fake_env = _fake_env(env)
136    fake_env.expect.that_bool(True).equals(True)
137    _assert_no_failures(fake_env, env = env)
138    fake_env.expect.that_bool(False).equals(False)
139    _assert_no_failures(fake_env, env = env)
140
141    fake_env.expect.that_bool(True).equals(False)
142    _assert_failure(fake_env, [
143        "expected: False",
144        "actual: True",
145    ], env = env)
146
147    fake_env.expect.that_bool(True, "MYEXPR").equals(False)
148    _assert_failure(fake_env, ["MYEXPR"], env = env)
149
150    subject = truth.expect(fake_env).that_bool(True)
151    subject.not_equals(True)
152    _assert_failure(
153        fake_env,
154        ["expected not to be: True", "actual: True"],
155        env = env,
156        msg = "check not_equals fails with same type",
157    )
158    subject.not_equals(None)
159    _assert_failure(
160        fake_env,
161        ["expected not to be: None (type: NoneType)", "actual: True (type: bool)"],
162        env = env,
163        msg = "check not_equals due to different type",
164    )
165    subject.not_equals(False)
166    _assert_no_failures(
167        fake_env,
168        env = env,
169        msg = "check BoolSubject.not_equals with unequal value of same type",
170    )
171
172    subject.is_in([True, False])
173    _assert_no_failures(
174        fake_env,
175        env = env,
176        msg = "check BoolSubject.is_in with matching values",
177    )
178    subject.is_in([None, 39])
179    _assert_failure(
180        fake_env,
181        ["expected any of:", "None", "39", "actual: True"],
182        env = env,
183        msg = "check is_in mismatched values",
184    )
185
186    _end(env, fake_env)
187
188_suite.append(bool_subject_test)
189
190def collection_custom_expr_test(name):
191    analysis_test(name, impl = _collection_custom_expr_test, target = "truth_tests_helper")
192
193def _collection_custom_expr_test(env, _target):
194    fake_env = _fake_env(env)
195    subject = fake_env.expect.that_collection(["a"], "MYEXPR")
196    subject.contains_exactly([])
197    _assert_failure(fake_env, ["MYEXPR"], env = env)
198    _end(env, fake_env)
199
200_suite.append(collection_custom_expr_test)
201
202def collection_has_size_test(name):
203    analysis_test(name, impl = _collection_has_size_test, target = "truth_tests_helper")
204
205def _collection_has_size_test(env, _target):
206    fake_env = _fake_env(env)
207    subject = fake_env.expect.that_collection(["a", "b", "c", "d"])
208
209    subject.has_size(4)
210    _assert_no_failures(
211        fake_env,
212        env = env,
213        msg = "check actual has expected size",
214    )
215
216    subject.has_size(0)
217    _assert_failure(
218        fake_env,
219        ["value of: collection.size()"],
220        env = env,
221        msg = "check actual does not have expected size",
222    )
223
224    _end(env, fake_env)
225
226_suite.append(collection_has_size_test)
227
228def collection_contains_test(name):
229    analysis_test(name, impl = _collection_contains_test, target = "truth_tests_helper")
230
231def _collection_contains_test(env, _target):
232    fake_env = _fake_env(env)
233    subject = fake_env.expect.that_collection(["a", "b", "c", "d"])
234
235    subject.contains("a")
236    _assert_no_failures(
237        fake_env,
238        env = env,
239        msg = "check actual does contain expected",
240    )
241
242    subject.contains("never")
243    _assert_failure(
244        fake_env,
245        ["expected to contain: never", "actual values", "0: a"],
246        env = env,
247        msg = "check actual is missing expected",
248    )
249
250    _end(env, fake_env)
251
252_suite.append(collection_contains_test)
253
254def collection_contains_predicate_test(name):
255    analysis_test(name, impl = _collection_contains_predicate_test, target = "truth_tests_helper")
256
257def _collection_contains_predicate_test(env, _target):
258    fake_env = _fake_env(env)
259    subject = truth.expect(fake_env).that_collection(["a", "b", "c", "d"])
260
261    subject.contains_predicate(matching.contains("a"))
262    _assert_no_failures(
263        fake_env,
264        env = env,
265        msg = "check actual does contains expected",
266    )
267
268    subject.contains_predicate(matching.contains("never"))
269    _assert_failure(
270        fake_env,
271        ["expected to contain: <contains never>", "actual values", "0: a"],
272        env = env,
273        msg = "check actual is missing a value",
274    )
275    _end(env, fake_env)
276
277_suite.append(collection_contains_predicate_test)
278
279def collection_contains_at_least_test(name):
280    analysis_test(name, impl = _collection_contains_at_least_test, target = "truth_tests_helper")
281
282def _collection_contains_at_least_test(env, _target):
283    fake_env = _fake_env(env)
284    subject = truth.expect(fake_env).that_collection(["a", "b", "c", "d"])
285
286    subject.contains_at_least(["a", "b", "c"]).in_order()
287    _assert_no_failures(
288        fake_env,
289        env = env,
290        msg = "check expected and actual with same elements in same order",
291    )
292
293    subject.contains_at_least(["never"])
294    _assert_failure(
295        fake_env,
296        ["expected elements missing", "never", "actual values", "0: a"],
297        env = env,
298        msg = "check actual is missing a value",
299    )
300
301    subject.contains_at_least([
302        "b",
303        "a",
304    ]).in_order()
305    _assert_failure(
306        fake_env,
307        [
308            "incorrect order",
309            "0: b found at offset 1",
310            "1: a found at offset 0",
311        ],
312        env = env,
313        msg = "check expected values present in wrong order",
314    )
315
316    _end(env, fake_env)
317
318_suite.append(collection_contains_at_least_test)
319
320def collection_contains_at_least_predicates_test(name):
321    analysis_test(name, impl = _collection_contains_at_least_predicates_test, target = "truth_tests_helper")
322
323def _collection_contains_at_least_predicates_test(env, _target):
324    fake_env = _fake_env(env)
325    subject = truth.expect(fake_env).that_collection(["a", "b", "c", "d"])
326    subject.contains_at_least_predicates([
327        matching.contains("a"),
328        matching.contains("b"),
329        matching.contains("c"),
330    ]).in_order()
331
332    subject.contains_at_least_predicates([
333        matching.never("never"),
334    ])
335    _assert_failure(
336        fake_env,
337        ["expected elements missing", "never", "actual values", "0: a"],
338        env = env,
339    )
340
341    subject.contains_at_least_predicates([
342        matching.custom("<MATCHER-B>", lambda v: "b" in v),
343        matching.custom("<MATCHER-A>", lambda v: "a" in v),
344    ]).in_order()
345    _assert_failure(
346        fake_env,
347        [
348            "incorrect order",
349            "0: <MATCHER-B> matched at offset 1 (matched: b)",
350            "1: <MATCHER-A> matched at offset 0 (matched: a)",
351        ],
352        env = env,
353    )
354
355    _end(env, fake_env)
356
357_suite.append(collection_contains_at_least_predicates_test)
358
359def collection_contains_exactly_test(name):
360    analysis_test(name, impl = _collection_contains_exactly_test, target = "truth_tests_helper")
361
362def _collection_contains_exactly_test(env, _target):
363    fake_env = _fake_env(env)
364
365    subject = truth.expect(fake_env).that_collection([])
366    subject.contains_exactly(["a"])
367    _assert_failure(
368        fake_env,
369        [
370            "1 missing:\n  0: a",
371            "expected exactly:\n  0: a",
372            "actual values:\n  <empty>",
373        ],
374        env = env,
375        msg = "check empty actual vs non-empty expected",
376    )
377
378    subject = truth.expect(fake_env).that_collection(["b"])
379    subject.contains_exactly([])
380    _assert_failure(
381        fake_env,
382        [
383            "1 unexpected:\n  0: b",
384            "expected exactly:\n  <empty>",
385            "actual values:\n  0: b",
386        ],
387        env = env,
388        msg = "check non-empty actual vs empty expected",
389    )
390
391    subject = truth.expect(fake_env).that_collection(["c"])
392    order = subject.contains_exactly(["c"])
393    _assert_no_failures(
394        fake_env,
395        env = env,
396        msg = "check expected and actual with same elements in same order",
397    )
398    order.in_order()
399    _assert_no_failures(
400        fake_env,
401        env = env,
402        msg = "check exact elements are in order",
403    )
404
405    subject = truth.expect(fake_env).that_collection(["d"])
406    subject.contains_exactly(["e"])
407    _assert_failure(
408        fake_env,
409        [
410            "1 missing:\n  0: e",
411            "1 unexpected:\n  0: d",
412            "expected exactly:\n  0: e",
413            "actual values:\n  0: d",
414        ],
415        env = env,
416        msg = "check disjoint values; same length",
417    )
418
419    subject = truth.expect(fake_env).that_collection(["f", "g"])
420    order = subject.contains_exactly(["g", "f"])
421    _assert_no_failures(
422        fake_env,
423        env = env,
424        msg = "check same elements with expected in different order",
425    )
426    order.in_order()
427    _assert_failure(
428        fake_env,
429        [
430            "expected values all found, but with incorrect order",
431            "0: g found at offset 1",
432            "1: f found at offset 0",
433            "actual values:",
434            "0: f",
435            "1: g",
436        ],
437        env = env,
438        msg = "check same elements out of order",
439    )
440
441    subject = truth.expect(fake_env).that_collection(["x", "y"])
442    subject.contains_exactly(["y"])
443    _assert_failure(
444        fake_env,
445        [
446            "1 unexpected:\n  0: x",
447            "expected exactly:\n  0: y",
448            "actual values:\n  0: x\n  1: y",
449        ],
450        env = env,
451        msg = "check expected subset of actual",
452    )
453
454    subject = truth.expect(fake_env).that_collection(["a", "b", "c", "d"])
455    subject.contains_exactly(["a", "b", "c", "d"])
456    _assert_no_failures(
457        fake_env,
458        env = env,
459        msg = "check expected and actual with exact elements and order; 4 values",
460    )
461
462    subject.contains_exactly(["d", "b", "a", "c"])
463    _assert_no_failures(
464        fake_env,
465        env = env,
466        msg = "check expected and actual same elements and different order; 4 values",
467    )
468
469    subject = truth.expect(fake_env).that_collection(["a", "b", "a"])
470    subject.contains_exactly(["a", "b", "a"])
471    _assert_no_failures(
472        fake_env,
473        env = env,
474        msg = "check multiplicity, same expected/actual order",
475    )
476
477    subject.contains_exactly(["b", "a", "a"])
478    _assert_no_failures(
479        fake_env,
480        env = env,
481        msg = "check multiplicity; different expected/actual order",
482    )
483
484    subject = truth.expect(fake_env).that_collection([
485        "one",
486        "two",
487        "one",
488        "three",
489        "one",
490        "four",
491    ])
492
493    subject.contains_exactly(["one", "two", "three", "five"])
494    _assert_failure(
495        fake_env,
496        [
497            ("1 missing:\n" +
498             "  0: five"),
499            ("3 unexpected:\n" +
500             "  0: four\n" +
501             "  1: one\n" +
502             "  2: one\n"),
503            ("expected exactly:\n" +
504             "  0: one\n" +
505             "  1: two\n" +
506             "  2: three\n" +
507             "  3: five\n"),
508        ],
509        env = env,
510        msg = "check multiplicity; expected with multiple, expected with unique",
511    )
512
513    subject = truth.expect(fake_env).that_collection(["one", "four", "three", "two", "five"])
514    order = subject.contains_exactly(["one", "two", "three", "four", "five"])
515    _assert_no_failures(
516        fake_env,
517        env = env,
518        msg = "check same elements with expected in different order",
519    )
520    order.in_order()
521    _assert_failure(
522        fake_env,
523        [
524            "expected values all found, but with incorrect order:",
525            "0: one found at offset 0",
526            "1: two found at offset 3",
527            "2: three found at offset 2",
528            "3: four found at offset 1",
529            "4: five found at offset 4",
530            "actual values:",
531            "0: one",
532            "1: four",
533            "2: three",
534            "3: two",
535            "4: five",
536        ],
537        env = env,
538        msg = "check same elements out of order",
539    )
540
541    _end(env, fake_env)
542
543_suite.append(collection_contains_exactly_test)
544
545def collection_contains_exactly_predicates_test(name):
546    analysis_test(name, impl = _collection_contains_exactly_predicates_test, target = "truth_tests_helper")
547
548def _collection_contains_exactly_predicates_test(env, _target):
549    fake_env = _fake_env(env)
550
551    subject = truth.expect(fake_env).that_collection([])
552    subject.contains_exactly_predicates([matching.contains("a")])
553    _assert_failure(
554        fake_env,
555        [
556            "1 missing:\n  0: <contains a>",
557            "expected exactly:\n  0: <contains a>",
558            "actual values:\n  <empty>",
559        ],
560        env = env,
561        msg = "check empty actual vs non-empty expected",
562    )
563
564    subject = truth.expect(fake_env).that_collection(["b"])
565    subject.contains_exactly_predicates([])
566    _assert_failure(
567        fake_env,
568        [
569            "1 unexpected:\n  0: b",
570            "expected exactly:\n  <empty>",
571            "actual values:\n  0: b",
572        ],
573        env = env,
574        msg = "check non-empty actual vs empty expected",
575    )
576
577    subject = truth.expect(fake_env).that_collection(["c"])
578    order = subject.contains_exactly_predicates([matching.contains("c")])
579    _assert_no_failures(
580        fake_env,
581        env = env,
582        msg = "check expected and actual with same elements in same order",
583    )
584    order.in_order()
585    _assert_no_failures(
586        fake_env,
587        env = env,
588        msg = "check exact elements are in order",
589    )
590
591    subject = truth.expect(fake_env).that_collection(["d"])
592    subject.contains_exactly_predicates([matching.contains("e")])
593    _assert_failure(
594        fake_env,
595        [
596            "1 missing:\n  0: <contains e>",
597            "1 unexpected:\n  0: d",
598            "expected exactly:\n  0: <contains e>",
599            "actual values:\n  0: d",
600        ],
601        env = env,
602        msg = "check disjoint values; same length",
603    )
604
605    subject = truth.expect(fake_env).that_collection(["f", "g"])
606    order = subject.contains_exactly_predicates([
607        matching.contains("g"),
608        matching.contains("f"),
609    ])
610    _assert_no_failures(
611        fake_env,
612        env = env,
613        msg = "check same elements with expected in different order",
614    )
615    order.in_order()
616    _assert_failure(
617        fake_env,
618        [
619            "expected values all found, but with incorrect order",
620            "0: <contains g> matched at offset 1 (matched: g)",
621            "1: <contains f> matched at offset 0 (matched: f)",
622            "actual values:",
623            "0: f",
624            "1: g",
625        ],
626        env = env,
627        msg = "check same elements out of order",
628    )
629
630    subject = truth.expect(fake_env).that_collection(["x", "y"])
631    subject.contains_exactly_predicates([matching.contains("y")])
632    _assert_failure(
633        fake_env,
634        [
635            "1 unexpected:\n  0: x",
636            "expected exactly:\n  0: <contains y>",
637            "actual values:\n  0: x\n  1: y",
638        ],
639        env = env,
640        msg = "check expected subset of actual",
641    )
642
643    subject = truth.expect(fake_env).that_collection(["a", "b", "c", "d"])
644    subject.contains_exactly_predicates([
645        matching.contains("a"),
646        matching.contains("b"),
647        matching.contains("c"),
648        matching.contains("d"),
649    ])
650    _assert_no_failures(
651        fake_env,
652        env = env,
653        msg = "check expected and actual with exact elements and order; 4 values",
654    )
655
656    subject.contains_exactly_predicates([
657        matching.contains("d"),
658        matching.contains("b"),
659        matching.contains("a"),
660        matching.contains("c"),
661    ])
662    _assert_no_failures(
663        fake_env,
664        env = env,
665        msg = "check expected and actual same elements and different order; 4 values",
666    )
667
668    subject = truth.expect(fake_env).that_collection(["a", "b", "a"])
669    subject.contains_exactly_predicates([
670        matching.contains("a"),
671        matching.contains("b"),
672        matching.contains("a"),
673    ])
674    _assert_no_failures(
675        fake_env,
676        env = env,
677        msg = "check multiplicity, same expected/actual order",
678    )
679
680    subject.contains_exactly_predicates([
681        matching.contains("b"),
682        matching.contains("a"),
683        matching.contains("a"),
684    ])
685    _assert_no_failures(
686        fake_env,
687        env = env,
688        msg = "check multiplicity; different expected/actual order",
689    )
690
691    subject = truth.expect(fake_env).that_collection([
692        "one",
693        "two",
694        "one",
695        "three",
696        "one",
697        "four",
698    ])
699
700    subject.contains_exactly_predicates([
701        matching.contains("one"),
702        matching.contains("two"),
703        matching.contains("three"),
704        matching.contains("five"),
705    ])
706    _assert_failure(
707        fake_env,
708        [
709            ("1 missing:\n" +
710             "  0: <contains five>"),
711            ("3 unexpected:\n" +
712             "  0: four\n" +
713             "  1: one\n" +
714             "  2: one\n"),
715            ("expected exactly:\n" +
716             "  0: <contains one>\n" +
717             "  1: <contains two>\n" +
718             "  2: <contains three>\n" +
719             "  3: <contains five>\n"),
720        ],
721        env = env,
722        msg = "check multiplicity; expected with multiple, expected with unique",
723    )
724    _end(env, fake_env)
725
726_suite.append(collection_contains_exactly_predicates_test)
727
728def collection_contains_none_of_test(name):
729    analysis_test(name, impl = _collection_contains_none_of_test, target = "truth_tests_helper")
730
731def _collection_contains_none_of_test(env, _target):
732    fake_env = _fake_env(env)
733    subject = truth.expect(fake_env).that_collection(["a"])
734
735    subject.contains_none_of(["b"])
736    _assert_no_failures(
737        fake_env,
738        env = env,
739        msg = "check actual contains none of",
740    )
741
742    subject.contains_none_of(["a"])
743    _assert_failure(
744        fake_env,
745        [
746            "expected not to contain any of:",
747            "  0: a",
748            "but 1 found",
749            "actual values:",
750        ],
751        env = env,
752        msg = "check actual contains an unexpected value",
753    )
754    _end(env, fake_env)
755
756_suite.append(collection_contains_none_of_test)
757
758def collection_not_contains_test(name):
759    analysis_test(name, impl = _collection_not_contains_test, target = "truth_tests_helper")
760
761def _collection_not_contains_test(env, _target):
762    fake_env = _fake_env(env)
763    subject = truth.expect(fake_env).that_collection(["a"])
764
765    subject.not_contains("b")
766    _assert_no_failures(
767        fake_env,
768        env = env,
769        msg = "check not_contains passes",
770    )
771    subject.not_contains("a")
772    _assert_failure(
773        fake_env,
774        [
775            "expected not to contain",
776            "0: a",
777        ],
778        env = env,
779        msg = "check not_contains fails",
780    )
781
782_suite.append(collection_not_contains_test)
783
784def collection_not_contains_predicate_test(name):
785    analysis_test(name, impl = _collection_not_contains_predicate_test, target = "truth_tests_helper")
786
787def _collection_not_contains_predicate_test(env, _target):
788    fake_env = _fake_env(env)
789    subject = truth.expect(fake_env).that_collection(["a"])
790
791    subject.not_contains_predicate(matching.contains("b"))
792    _assert_no_failures(
793        fake_env,
794        env = env,
795        msg = "check actual does not contain a value",
796    )
797
798    subject.not_contains_predicate(matching.contains("a"))
799    _assert_failure(
800        fake_env,
801        ["expected not to contain any of: <contains a>", "but 1 found:", "0: a"],
802        env = env,
803        msg = "check actual contains an unexpected value",
804    )
805    _end(env, fake_env)
806
807_suite.append(collection_not_contains_predicate_test)
808
809def collection_offset_test(name):
810    analysis_test(name, impl = _collection_offset_test, target = "truth_tests_helper")
811
812def _collection_offset_test(env, _target):
813    fake_env = _fake_env(env)
814    subject = truth.expect(fake_env).that_collection(["a", "b", "c"])
815
816    offset_value = subject.offset(0, factory = lambda v, meta: v)
817    ut_asserts.true(env, offset_value == "a", "unexpected offset value at 0")
818
819    offset_value = subject.offset(-1, factory = lambda v, meta: v)
820    ut_asserts.true(env, offset_value == "c", "unexpected offset value at -1")
821
822    subject.offset(1, factory = subjects.str).equals("not-b")
823
824    _assert_failure(
825        fake_env,
826        [".offset(1)"],
827        env = env,
828        msg = "offset error message context not found",
829    )
830
831    _end(env, fake_env)
832
833_suite.append(collection_offset_test)
834
835def _collection_transform_test(name):
836    analysis_test(name, impl = _collection_transform_test_impl, target = "truth_tests_helper")
837
838def _collection_transform_test_impl(env, target):
839    _ = target  # @unused
840    fake_env = _fake_env(env)
841    starter = truth.expect(fake_env).that_collection(["alan", "bert", "cari"])
842
843    actual = starter.transform(
844        "values that contain a",
845        filter = lambda v: "a" in v,
846    )
847    actual.contains("not-present")
848    _assert_failure(
849        fake_env,
850        [
851            "transform()",
852            "0: alan",
853            "1: cari",
854            "transform: values that contain a",
855        ],
856        env = env,
857        msg = "transform with lambda filter",
858    )
859
860    actual = starter.transform(filter = matching.contains("b"))
861    actual.contains("not-present")
862    _assert_failure(
863        fake_env,
864        [
865            "0: bert",
866            "transform: filter=<contains b>",
867        ],
868        env = env,
869        msg = "transform with matcher filter",
870    )
871
872    def contains_c(v):
873        return "c" in v
874
875    actual = starter.transform(filter = contains_c)
876    actual.contains("not-present")
877    _assert_failure(
878        fake_env,
879        [
880            "0: cari",
881            "transform: filter=contains_c(...)",
882        ],
883        env = env,
884        msg = "transform with named function filter",
885    )
886
887    actual = starter.transform(
888        "v.upper(); match even offsets",
889        map_each = lambda v: "{}-{}".format(v[0], v[1].upper()),
890        loop = enumerate,
891    )
892    actual.contains("not-present")
893    _assert_failure(
894        fake_env,
895        [
896            "transform()",
897            "0: 0-ALAN",
898            "1: 1-BERT",
899            "2: 2-CARI",
900            "transform: v.upper(); match even offsets",
901        ],
902        env = env,
903        msg = "transform with all args",
904    )
905
906    _end(env, fake_env)
907
908_suite.append(_collection_transform_test)
909
910def execution_info_test(name):
911    analysis_test(name, impl = _execution_info_test, target = "truth_tests_helper")
912
913def _execution_info_test(env, target):
914    # TODO(rlevasseur): Remove this after cl/474597236 is released in Blaze
915    exec_info_is_ctor = str(testing.ExecutionInfo) == "<function ExecutionInfo>"
916    if not exec_info_is_ctor:
917        return
918    fake_env = _fake_env(env)
919
920    subject = truth.expect(fake_env).that_target(target).provider(testing.ExecutionInfo)
921    subject.requirements().contains_exactly({"EIKEY1": "EIVALUE1"})
922    _assert_no_failures(fake_env, env = env)
923    if _IS_BAZEL_6_OR_HIGHER:
924        subject.exec_group().equals("THE_EXEC_GROUP")
925    _assert_no_failures(fake_env, env = env)
926    _end(env, fake_env)
927
928_suite.append(execution_info_test)
929
930def depset_file_subject_test(name):
931    analysis_test(name, impl = _depset_file_subject_test, target = "truth_tests_data_files")
932
933def _depset_file_subject_test(env, target):
934    fake_env = _fake_env(env)
935
936    # We go through a target so that the usual format_str kwargs are present.
937    subject = truth.expect(fake_env).that_target(target).default_outputs()
938
939    # The CollectionSubject tests cover contains_at_least_predicates in
940    # more depth, so just do some basic tests here.
941    subject.contains_at_least_predicates([
942        matching.file_path_matches("txt"),
943    ])
944    _assert_no_failures(fake_env, env = env)
945
946    subject.contains_at_least_predicates([
947        matching.file_path_matches("NOT THERE"),
948    ])
949    _assert_failure(
950        fake_env,
951        ["NOT THERE", "file1.txt"],
952        env = env,
953    )
954
955    subject.contains_predicate(matching.file_path_matches("txt"))
956    _assert_no_failures(fake_env, env = env)
957    subject.contains_predicate(matching.file_path_matches("NOT THERE"))
958    _assert_failure(
959        fake_env,
960        ["NOT THERE", "file1.txt"],
961        env = env,
962    )
963
964    subject.contains_exactly(["{package}/testdata/file1.txt"])
965    _assert_no_failures(fake_env, env = env)
966    subject.contains_exactly(["NOT THERE"])
967    _assert_failure(
968        fake_env,
969        ["NOT THERE", "file1.txt"],
970        env = env,
971    )
972
973    subject.not_contains("does-not-contain")
974    _assert_no_failures(
975        fake_env,
976        env = env,
977        msg = "DepsetFilesubject.not_contains success test",
978    )
979    subject.not_contains("{package}/testdata/file1.txt")
980    _assert_failure(
981        fake_env,
982        ["expected not to contain any of", "file1.txt"],
983        env = env,
984        msg = "DepsetFilesubject.not_contains failure test",
985    )
986
987    _end(env, fake_env)
988
989_suite.append(depset_file_subject_test)
990
991def dict_subject_test(name):
992    analysis_test(name, impl = _dict_subject_test, target = "truth_tests_helper")
993
994def _dict_subject_test(env, _target):
995    fake_env = _fake_env(env)
996    subject = truth.expect(fake_env).that_dict({"a": 1, "b": 2, "c": 3})
997
998    def factory(value, *, meta):
999        return struct(value = value, meta = meta)
1000
1001    actual = subject.get("a", factory = factory)
1002
1003    truth.expect(env).that_int(actual.value).equals(1)
1004    truth.expect(env).that_collection(actual.meta._exprs).contains("get(a)")
1005
1006    subject.contains_exactly({"a": 1, "b": 2, "c": 3})
1007    _assert_no_failures(fake_env, env = env)
1008
1009    subject.contains_exactly({"d": 4, "a": 99})
1010    _assert_failure(
1011        fake_env,
1012        [
1013            ("expected dict: {\n" +
1014             "  a: <int 99>\n" +
1015             "  d: <int 4>\n" +
1016             "}\n"),
1017            ("1 missing keys:\n" +
1018             "  0: d\n"),
1019            ("2 unexpected keys:\n" +
1020             "  0: b\n" +
1021             "  1: c\n"),
1022            ("1 incorrect entries:\n" +
1023             "key a:\n" +
1024             "  expected: 99\n" +
1025             "  but was : 1\n"),
1026            ("actual: {\n" +
1027             "  a: <int 1>\n" +
1028             "  b: <int 2>\n" +
1029             "  c: <int 3>\n" +
1030             "}\n"),
1031        ],
1032        env = env,
1033    )
1034
1035    subject.contains_at_least({"a": 1})
1036    _assert_no_failures(fake_env, env = env)
1037
1038    subject.contains_at_least({"d": 91, "a": 74})
1039    _assert_failure(
1040        fake_env,
1041        [
1042            ("expected dict: {\n" +
1043             "  a: <int 74>\n" +
1044             "  d: <int 91>\n" +
1045             "}\n"),
1046            ("1 missing keys:\n" +
1047             "  0: d\n"),
1048            ("1 incorrect entries:\n" +
1049             "key a:\n" +
1050             "  expected: 74\n" +
1051             "  but was : 1\n"),
1052            ("actual: {\n" +
1053             "  a: <int 1>\n" +
1054             "  b: <int 2>\n" +
1055             "  c: <int 3>\n" +
1056             "}\n"),
1057        ],
1058        env = env,
1059    )
1060
1061    # NOTE: we use the real env for this, since we're doing a real assert
1062    truth.expect(env).that_collection(
1063        subject.keys().actual,
1064    ).contains_exactly(["a", "b", "c"])
1065
1066    _end(env, fake_env)
1067
1068_suite.append(dict_subject_test)
1069
1070def expect_test(name):
1071    analysis_test(name, impl = _expect_test, target = "truth_tests_helper")
1072
1073def _expect_test(env, target):
1074    fake_env = _fake_env(env)
1075    expect = truth.expect(fake_env)
1076
1077    ut_asserts.true(
1078        env,
1079        expect.that_target(target) != None,
1080        msg = "expect.that_target",
1081    )
1082    _assert_no_failures(fake_env, env = env)
1083
1084    expect.where(
1085        foo = "bar",
1086        baz = "qux",
1087    ).that_bool(True).equals(False)
1088    _assert_failure(
1089        fake_env,
1090        ["foo: bar", "baz: qux"],
1091        env = env,
1092    )
1093    _end(env, fake_env)
1094
1095_suite.append(expect_test)
1096
1097def file_subject_test(name):
1098    analysis_test(name, impl = _file_subject_test, target = "truth_tests_data_files")
1099
1100def _file_subject_test(env, target):
1101    fake_env = _fake_env(env)
1102    package = target.label.package
1103    expect = truth.expect(fake_env)
1104    subject = expect.that_file(target.files.to_list()[0])
1105    subject.short_path_equals(package + "/testdata/file1.txt")
1106    _assert_no_failures(fake_env, env = env)
1107
1108    subject.short_path_equals("landon-and-hope-forever.txt")
1109    _assert_failure(
1110        fake_env,
1111        [
1112            "value of: file",
1113            "expected: landon-and-hope-forever.txt",
1114            "actual: {}/testdata/file1.txt".format(package),
1115        ],
1116        env = env,
1117    )
1118
1119    subject = expect.that_file(
1120        target.files.to_list()[0],
1121        meta = expect.meta.derive(
1122            format_str_kwargs = {"custom": "file1.txt"},
1123        ),
1124    )
1125
1126    # NOTE: We purposefully don't use `{package}` because we're just
1127    # testing the `{custom}` keyword
1128    subject.short_path_equals(package + "/testdata/{custom}")
1129    _assert_no_failures(fake_env, env = env)
1130
1131    _end(env, fake_env)
1132
1133_suite.append(file_subject_test)
1134
1135def label_subject_test(name):
1136    analysis_test(name, impl = _label_subject_test, target = "truth_tests_helper")
1137
1138def _label_subject_test(env, target):
1139    fake_env = _fake_env(env)
1140
1141    expect = truth.expect(fake_env)
1142    subject = expect.that_target(target).label()
1143
1144    subject.equals("//tests:truth_tests_helper")
1145    _assert_no_failures(fake_env, env = env)
1146
1147    subject.equals(Label("//tests:truth_tests_helper"))
1148    _assert_no_failures(fake_env, env = env)
1149
1150    subject.equals("//nope")
1151    _assert_failure(
1152        fake_env,
1153        ["expected: " + str(Label("//nope")), "actual:", "_helper"],
1154        env = env,
1155    )
1156
1157    subject = subjects.label(Label("//some/pkg:label"), expect.meta)
1158    subject.is_in(["//foo:bar", "//some/pkg:label"])
1159    _assert_no_failures(fake_env, msg = "is_in with matched str values", env = env)
1160    subject.is_in([Label("//bar:baz"), Label("//some/pkg:label")])
1161    _assert_no_failures(fake_env, msg = "is_in with matched label values", env = env)
1162    subject.is_in(["//not:there", Label("//other:value")])
1163    _assert_failure(
1164        fake_env,
1165        [
1166            "expected any of:",
1167            "//not:there",
1168            "//other:value",
1169            "actual: " + str(Label("//some/pkg:label")),
1170        ],
1171        msg = "check is_in fails",
1172        env = env,
1173    )
1174
1175    _end(env, fake_env)
1176
1177_suite.append(label_subject_test)
1178
1179def runfiles_subject_test(name):
1180    analysis_test(name, impl = _runfiles_subject_test, target = "truth_tests_helper")
1181
1182def _runfiles_subject_test(env, target):
1183    fake_env = _fake_env(env)
1184
1185    subject = truth.expect(fake_env).that_target(target).runfiles()
1186    subject.contains("{workspace}/{package}/default_runfile1.txt")
1187    _assert_no_failures(fake_env, env = env)
1188
1189    subject.contains("does-not-exist")
1190    _assert_failure(
1191        fake_env,
1192        [
1193            "expected to contain: does-not-exist",
1194            "actual default runfiles:",
1195            "default_runfile1.txt",
1196            "target: ".format(target.label),
1197        ],
1198        env = env,
1199        msg = "check contains",
1200    )
1201
1202    subject.contains_none_of(["{workspace}/{package}/not-there.txt"])
1203    _assert_no_failures(fake_env, env = env)
1204
1205    subject.contains_none_of(["{workspace}/{package}/default_runfile1.txt"])
1206    _assert_failure(
1207        fake_env,
1208        [
1209            "expected not to contain any of",
1210            "default_runfile1.txt",
1211            env.ctx.workspace_name,
1212        ],
1213        env = env,
1214        msg = "check contains none of",
1215    )
1216
1217    subject.contains_exactly([
1218        "{workspace}/{package}/default_runfile1.txt",
1219        "{workspace}/{package}/truth_tests_helper.txt",
1220    ])
1221    _assert_no_failures(fake_env, env = env)
1222    subject.contains_exactly([
1223        "{workspace}/{package}/not-there.txt",
1224    ])
1225    _assert_failure(
1226        fake_env,
1227        [
1228            "1 missing",
1229            "not-there.txt",
1230            env.ctx.workspace_name,
1231        ],
1232        env = env,
1233        msg = "check contains_exactly fails",
1234    )
1235
1236    subject.contains_at_least([
1237        "{workspace}/{package}/default_runfile1.txt",
1238    ])
1239    _assert_no_failures(fake_env, env = env)
1240    subject.contains_at_least([
1241        "not-there.txt",
1242    ])
1243    _assert_failure(
1244        fake_env,
1245        [
1246            "1 expected paths missing",
1247            "not-there.txt",
1248            env.ctx.workspace_name,
1249        ],
1250        env = env,
1251        msg = "check contains_at_least fails",
1252    )
1253
1254    _end(env, fake_env)
1255
1256_suite.append(runfiles_subject_test)
1257
1258def str_subject_test(name):
1259    analysis_test(name, impl = _str_subject_test, target = "truth_tests_helper")
1260
1261def _str_subject_test(env, _target):
1262    fake_env = _fake_env(env)
1263    subject = truth.expect(fake_env).that_str("foobar")
1264
1265    subject.contains("ob")
1266    _assert_no_failures(fake_env, env = env)
1267
1268    subject.contains("nope")
1269    _assert_failure(
1270        fake_env,
1271        ["expected to contain: nope", "actual: foobar"],
1272        env = env,
1273        msg = "check contains",
1274    )
1275
1276    subject.equals("foobar")
1277    _assert_no_failures(fake_env, env = env)
1278
1279    subject.equals("not foobar")
1280    _assert_failure(
1281        fake_env,
1282        ["expected: not foobar", "actual: foobar"],
1283        env = env,
1284        msg = "check equals",
1285    )
1286
1287    result = subject.split("b")
1288    ut_asserts.true(env, result.actual == ["foo", "ar"], "incorrectly split")
1289
1290    subject.not_equals("foobar")
1291    _assert_failure(
1292        fake_env,
1293        ["expected not to be: foobar", "actual: foobar"],
1294        env = env,
1295        msg = "check not_equals with equal value",
1296    )
1297    subject.not_equals(47)
1298    _assert_failure(
1299        fake_env,
1300        ["expected not to be: 47 (type: int)", "actual: foobar (type: string)"],
1301        env = env,
1302        msg = "check not_equals with different type",
1303    )
1304    subject.not_equals("not-foobar")
1305    _assert_no_failures(
1306        fake_env,
1307        env = env,
1308        msg = "check not_equals with unequal value of same type",
1309    )
1310
1311    subject.is_in(["xxx", "yyy", "zzz"])
1312    _assert_failure(
1313        fake_env,
1314        ["expected any of:", "xxx", "yyy", "zzz", "actual: foobar"],
1315        env = env,
1316        msg = "check is_in with non-matching values",
1317    )
1318    subject.is_in(["foobar", "y", "z"])
1319    _assert_no_failures(
1320        fake_env,
1321        env = env,
1322        msg = "check is_in with matching values",
1323    )
1324    _end(env, fake_env)
1325
1326_suite.append(str_subject_test)
1327
1328def target_subject_test(name):
1329    analysis_test(name, impl = _target_subject_test, target = "truth_tests_helper")  #TODO also file target
1330
1331def _target_subject_test(env, target):
1332    fake_env = _fake_env(env)
1333    subject = truth.expect(fake_env).that_target(target)
1334
1335    # First a static string, no formatting parameters
1336    result = subject.action_generating("{package}/default_runfile1.txt")
1337    ut_asserts.true(env, result != None, msg = "action_generating gave None")
1338
1339    # Now try it with formatting parameters
1340    result = subject.action_generating("{package}/{name}.txt")
1341    ut_asserts.true(env, result != None, msg = "action_generating gave None")
1342
1343    result = subject.label()
1344    ut_asserts.true(env, result != None, msg = "label gave None")
1345
1346    subject = truth.expect(fake_env).that_target(target)
1347
1348    tags = subject.tags()
1349    ut_asserts.true(env, tags != None, msg = "tags gave None")
1350
1351    tags.contains_exactly(["tag1", "tag2"])
1352    _assert_no_failures(
1353        fake_env,
1354        env = env,
1355        msg = "check TargetSubject.tags()",
1356    )
1357
1358    attr_subject = subject.attr("testonly")
1359    ut_asserts.true(env, attr_subject != None, msg = "attr(testonly) gave None")
1360
1361    custom_subject = subject.attr(
1362        "testonly",
1363        factory = lambda v, meta: struct(custom = True),
1364    )
1365    ut_asserts.true(
1366        env,
1367        custom_subject.custom == True,
1368        msg = "attr() with custom factory gave wrong value",
1369    )
1370
1371    output_group_subject = subject.output_group("some_group")
1372    output_group_subject.contains("{package}/output_group_file.txt")
1373    _assert_no_failures(
1374        fake_env,
1375        env = env,
1376        msg = "check TargetSubject.output_group()",
1377    )
1378
1379    _end(env, fake_env)
1380
1381_suite.append(target_subject_test)
1382
1383def run_environment_info_subject_test(name):
1384    analysis_test(name, impl = _run_environment_info_subject_test, target = "truth_tests_helper")
1385
1386def _run_environment_info_subject_test(env, target):
1387    fake_env = _fake_env(env)
1388
1389    subject = truth.expect(fake_env).that_target(target).provider(
1390        RunEnvironmentInfo,
1391    )
1392
1393    subject.environment().contains_exactly({
1394        "EKEY1": "EVALUE1",
1395        "EKEY2": "EVALUE2",
1396    })
1397    _assert_no_failures(fake_env, env = env)
1398
1399    subject.inherited_environment().contains_exactly(["INHERIT1", "INHERIT2"])
1400    _assert_no_failures(fake_env, env = env)
1401
1402    _end(env, fake_env)
1403
1404_suite.append(run_environment_info_subject_test)
1405
1406def _add_failure_works_test(name):
1407    analysis_test(
1408        name,
1409        impl = _add_failure_works_test_impl,
1410        target = "truth_tests_noop",
1411        expect_failure = True,
1412    )
1413
1414def _add_failure_works_test_impl(env, target):
1415    """Test that the real add_failure() codepath works.
1416
1417    All the other tests mock out the fail() call, this is the one test that doesn't.
1418    """
1419    _ = target  # @unused
1420
1421    # NOTE: this prints a spurious message.
1422    env.expect.meta.add_failure("FAKE PROBLEM", "FAKE ACTUAL")
1423
1424    failures = list(env._failures)
1425    env._failures.clear()
1426
1427    if len(failures) != 1:
1428        env.fail("Expected len(failures) == 1, got " + str(len(failures)))
1429    else:
1430        failure = failures[0]
1431        if "FAKE PROBLEM" not in failure:
1432            env.fail("Expected string 'FAKE PROBLEM' not found in failure message")
1433        if "FAKE ACTUAL" not in failure:
1434            env.fail("Expected string 'FAKE ACTUAL' not found in failure message")
1435
1436_suite.append(_add_failure_works_test)
1437
1438def _assert_no_failures(fake_env, *, env, msg = ""):
1439    fail_lines = [
1440        "expected no failures, but found failures",
1441        msg,
1442        "===== FAILURE MESSAGES =====",
1443    ]
1444    fail_lines.extend(fake_env.failures)
1445    fail_lines.append("===== END FAILURE MESSAGES ====")
1446    fail_msg = "\n".join(fail_lines)
1447    ut_asserts.true(env, len(fake_env.failures) == 0, msg = fail_msg)
1448    fake_env.reset()
1449
1450def _assert_failure(fake_env, expected_strs, *, env, msg = ""):
1451    if len(fake_env.failures) != 1:
1452        env.fail("expected exactly 1 failure, but found {}".format(len(fake_env.failures)))
1453
1454    if len(fake_env.failures) > 0:
1455        failure = fake_env.failures[0]
1456        for expected in expected_strs:
1457            if expected not in failure:
1458                env.fail((
1459                    "\nFailure message incorrect:\n{}\n" +
1460                    "===== EXPECTED ERROR SUBSTRING =====\n{}\n" +
1461                    "===== END EXPECTED ERROR SUBSTRING =====\n" +
1462                    "===== ACTUAL FAILURE MESSAGE =====\n{}\n" +
1463                    "===== END ACTUAL FAILURE MESSAGE ====="
1464                ).format(
1465                    msg,
1466                    expected,
1467                    failure,
1468                ))
1469
1470    fake_env.reset()
1471
1472def _test_helper_impl(ctx):
1473    action_output = ctx.actions.declare_file("action.txt")
1474    ctx.actions.run(
1475        outputs = [action_output],
1476        executable = ctx.executable.tool,
1477        arguments = [
1478            "arg1",
1479            "--boolflag",
1480            "--arg1flag",
1481            "arg1value",
1482            "--arg2flag=arg2value",
1483        ],
1484        mnemonic = "Action1",
1485    )
1486    if _IS_BAZEL_6_OR_HIGHER:
1487        exec_info_bazel_6_kwargs = {"exec_group": "THE_EXEC_GROUP"}
1488    else:
1489        exec_info_bazel_6_kwargs = {}
1490
1491    return [
1492        DefaultInfo(
1493            default_runfiles = ctx.runfiles(
1494                files = [
1495                    _empty_file(ctx, "default_runfile1.txt"),
1496                    _empty_file(ctx, ctx.label.name + ".txt"),
1497                ],
1498            ),
1499        ),
1500        testing.TestEnvironment(
1501            environment = {"EKEY1": "EVALUE1", "EKEY2": "EVALUE2"},
1502            inherited_environment = ["INHERIT1", "INHERIT2"],
1503        ),
1504        testing.ExecutionInfo({"EIKEY1": "EIVALUE1"}, **exec_info_bazel_6_kwargs),
1505        OutputGroupInfo(
1506            some_group = depset([_empty_file(ctx, "output_group_file.txt")]),
1507        ),
1508    ]
1509
1510test_helper = rule(
1511    implementation = _test_helper_impl,
1512    attrs = {
1513        "tool": attr.label(
1514            default = ":truth_tests_noop",
1515            executable = True,
1516            cfg = "exec",
1517        ),
1518    },
1519)
1520
1521def _empty_file(ctx, name):
1522    file = ctx.actions.declare_file(name)
1523    ctx.actions.write(file, content = "")
1524    return file
1525
1526def _noop_binary_impl(ctx):
1527    return DefaultInfo(executable = _empty_file(ctx, ctx.label.name))
1528
1529noop_binary = rule(
1530    implementation = _noop_binary_impl,
1531    executable = True,
1532)
1533
1534def truth_test_suite(name):
1535    # Unit tests can't directly create File objects, so we have a generic
1536    # collection of files they can put in custom attributes to use.
1537    native.filegroup(
1538        name = "truth_tests_data_files",
1539        srcs = native.glob(["testdata/**"]),
1540    )
1541    test_helper(
1542        name = "truth_tests_helper",
1543        tags = ["tag1", "tag2"],
1544    )
1545    noop_binary(name = "truth_tests_noop")
1546
1547    test_suite(
1548        name = name,
1549        tests = _suite,
1550    )
1551