xref: /aosp_15_r20/external/pytorch/test/HowToWriteTestsUsingFileCheck.md (revision da0073e96a02ea20f0ac840b70461e3646d07c45)
1*da0073e9SAndroid Build Coastguard Worker# How to write tests using FileCheck
2*da0073e9SAndroid Build Coastguard Worker
3*da0073e9SAndroid Build Coastguard Worker## What is FileCheck
4*da0073e9SAndroid Build Coastguard Worker
5*da0073e9SAndroid Build Coastguard WorkerFileCheck can be seen as an advanced version of grep. We use it for writing
6*da0073e9SAndroid Build Coastguard Workersmall annotated unit tests for optimization passes. FileCheck used in PyTorch is
7*da0073e9SAndroid Build Coastguard Workerinspired by [LLVM FileCheck
8*da0073e9SAndroid Build Coastguard WorkerTool](https://llvm.org/docs/CommandGuide/FileCheck.html), but is not the same.
9*da0073e9SAndroid Build Coastguard WorkerFileCheck is available for writing both C++ and python tests.
10*da0073e9SAndroid Build Coastguard Worker
11*da0073e9SAndroid Build Coastguard Worker## How does it work
12*da0073e9SAndroid Build Coastguard Worker
13*da0073e9SAndroid Build Coastguard WorkerLet's look at a test written with FileCheck. The following test verifies that
14*da0073e9SAndroid Build Coastguard WorkerCSE pass removes one out of two similar `aten::mul` nodes. Here is how the test
15*da0073e9SAndroid Build Coastguard Workerlooks like:
16*da0073e9SAndroid Build Coastguard Worker
17*da0073e9SAndroid Build Coastguard Worker```python
18*da0073e9SAndroid Build Coastguard Workerdef test_cse():
19*da0073e9SAndroid Build Coastguard Worker    input_str = """graph(%a : Tensor, %b : Tensor):
20*da0073e9SAndroid Build Coastguard Worker      # CHECK: aten::mul
21*da0073e9SAndroid Build Coastguard Worker      %x : Tensor = aten::mul(%a, %b)
22*da0073e9SAndroid Build Coastguard Worker      # Check that the second aten::mul is removed by CSE.
23*da0073e9SAndroid Build Coastguard Worker      # CHECK-NOT: aten::mul
24*da0073e9SAndroid Build Coastguard Worker      %y : Tensor = aten::mul(%a, %b)
25*da0073e9SAndroid Build Coastguard Worker      # CHECK: return
26*da0073e9SAndroid Build Coastguard Worker      return (%x, %y)
27*da0073e9SAndroid Build Coastguard Worker      """
28*da0073e9SAndroid Build Coastguard Worker    parsed = parse_ir(input_str)
29*da0073e9SAndroid Build Coastguard Worker    optimized = run_cse(parsed)
30*da0073e9SAndroid Build Coastguard Worker    FileCheck().run(input_str, optimized)
31*da0073e9SAndroid Build Coastguard Worker```
32*da0073e9SAndroid Build Coastguard Worker
33*da0073e9SAndroid Build Coastguard WorkerLet's look in detail at how it works. First, the input string is parsed by
34*da0073e9SAndroid Build Coastguard Worker`parse_ir`. At that stage all annotations are ignored since they are written in
35*da0073e9SAndroid Build Coastguard Workercomments, so this is what parser essentially sees:
36*da0073e9SAndroid Build Coastguard Worker
37*da0073e9SAndroid Build Coastguard Worker```
38*da0073e9SAndroid Build Coastguard Workergraph(%a : Tensor, %b : Tensor):
39*da0073e9SAndroid Build Coastguard Worker      %x : Tensor = aten::mul(%a, %b)
40*da0073e9SAndroid Build Coastguard Worker      %y : Tensor = aten::mul(%a, %b)
41*da0073e9SAndroid Build Coastguard Worker      return (%x, %y)
42*da0073e9SAndroid Build Coastguard Worker```
43*da0073e9SAndroid Build Coastguard Worker
44*da0073e9SAndroid Build Coastguard WorkerWe then run CSE on the parsed IR and expect it to remove the second `aten::mul`,
45*da0073e9SAndroid Build Coastguard Workerwhich is redundant. After CSE our IR looks like this:
46*da0073e9SAndroid Build Coastguard Worker
47*da0073e9SAndroid Build Coastguard Worker```
48*da0073e9SAndroid Build Coastguard Workergraph(%a : Tensor, %b : Tensor):
49*da0073e9SAndroid Build Coastguard Worker      %x : Tensor = aten::mul(%a, %b)
50*da0073e9SAndroid Build Coastguard Worker      return (%x, %x)
51*da0073e9SAndroid Build Coastguard Worker```
52*da0073e9SAndroid Build Coastguard Worker
53*da0073e9SAndroid Build Coastguard WorkerAnd now we run `FileCheck` passing to it both original input string and the
54*da0073e9SAndroid Build Coastguard Workeroptimized IR. From the input string `FileCheck` ignores everything except `#
55*da0073e9SAndroid Build Coastguard WorkerCHECK` pragmas and essentially it sees the input string like this:
56*da0073e9SAndroid Build Coastguard Worker
57*da0073e9SAndroid Build Coastguard Worker```
58*da0073e9SAndroid Build Coastguard Worker      # CHECK: aten::mul       (1)
59*da0073e9SAndroid Build Coastguard Worker      # CHECK-NOT: aten::mul   (2)
60*da0073e9SAndroid Build Coastguard Worker      # CHECK: return          (3)
61*da0073e9SAndroid Build Coastguard Worker```
62*da0073e9SAndroid Build Coastguard Worker
63*da0073e9SAndroid Build Coastguard WorkerIt then checks that the optimized IR satisfies the specified annotations. It
64*da0073e9SAndroid Build Coastguard Workerfirst finds string `%x : Tensor = aten::mul(%a, %b)` matching the annotation (1),
65*da0073e9SAndroid Build Coastguard Workerthen it finds string `return (%x, %x)` matching the annotation (3), and since
66*da0073e9SAndroid Build Coastguard Workerthere were no lines matching `aten::mul` after the match (1) and before the
67*da0073e9SAndroid Build Coastguard Workermatch (3), the annotation (2) is also satisfied.
68*da0073e9SAndroid Build Coastguard Worker
69*da0073e9SAndroid Build Coastguard WorkerOne could also register FileCheck annotations using a builder API. To generate
70*da0073e9SAndroid Build Coastguard Workerannotations from the example above one would write:
71*da0073e9SAndroid Build Coastguard Worker```python
72*da0073e9SAndroid Build Coastguard Worker      FileCheck().check("aten::mul")     \
73*da0073e9SAndroid Build Coastguard Worker                 .check_not("aten::mul") \
74*da0073e9SAndroid Build Coastguard Worker                 .check("return")        \
75*da0073e9SAndroid Build Coastguard Worker                 .run(optimized)
76*da0073e9SAndroid Build Coastguard Worker```
77*da0073e9SAndroid Build Coastguard Worker
78*da0073e9SAndroid Build Coastguard Worker## Supported pragmas
79*da0073e9SAndroid Build Coastguard Worker
80*da0073e9SAndroid Build Coastguard Worker* `CHECK: <pattern>`
81*da0073e9SAndroid Build Coastguard Worker  Scans the input until `PATTERN` is found. Fails if the pattern is not found.
82*da0073e9SAndroid Build Coastguard Worker* `CHECK-NEXT: <pattern>`
83*da0073e9SAndroid Build Coastguard Worker  Scans the input on the line immediately following the previous CHECK until
84*da0073e9SAndroid Build Coastguard Worker  `PATTERN` is found. Fails if the pattern is not found on that line.
85*da0073e9SAndroid Build Coastguard Worker* `CHECK-NOT: <pattern>`
86*da0073e9SAndroid Build Coastguard Worker  Scans the input and fails if `PATTERN` is found on any line. The scan stops when
87*da0073e9SAndroid Build Coastguard Worker  a match for a next `CHECK` is found.
88*da0073e9SAndroid Build Coastguard Worker* `CHECK-SAME: <pattern>`
89*da0073e9SAndroid Build Coastguard Worker  Checks that PATTERN is found in the line of the last match.
90*da0073e9SAndroid Build Coastguard Worker* `CHECK-COUNT-<num>: <pattern>`
91*da0073e9SAndroid Build Coastguard Worker  Scans the input and succeeds when a line containing at least `NUM` entries of
92*da0073e9SAndroid Build Coastguard Worker  `PATTERN` is found.
93*da0073e9SAndroid Build Coastguard Worker* `CHECK-COUNT-EXACTLY-<num>: <pattern>`
94*da0073e9SAndroid Build Coastguard Worker  Scans the input and succeeds when a line containing exactly `NUM` entries of
95*da0073e9SAndroid Build Coastguard Worker  `PATTERN` is found.
96*da0073e9SAndroid Build Coastguard Worker* `CHECK-DAG: <pattern>`
97*da0073e9SAndroid Build Coastguard Worker  Works similar to the usual `CHECK` pragma, but also matches if there exists a
98*da0073e9SAndroid Build Coastguard Worker  way to reorder the CHECK-DAG pragmas to satisfy all patterns.
99*da0073e9SAndroid Build Coastguard Worker  For example the following pattern:
100*da0073e9SAndroid Build Coastguard Worker  ```
101*da0073e9SAndroid Build Coastguard Worker  # CHECK: foo
102*da0073e9SAndroid Build Coastguard Worker  # CHECK-DAG: bar
103*da0073e9SAndroid Build Coastguard Worker  # CHECK-DAG: ham
104*da0073e9SAndroid Build Coastguard Worker  # CHECK: end
105*da0073e9SAndroid Build Coastguard Worker  ```
106*da0073e9SAndroid Build Coastguard Worker  would match the following input (note that `ham` and `bar` are swapped):
107*da0073e9SAndroid Build Coastguard Worker  ```
108*da0073e9SAndroid Build Coastguard Worker  foo
109*da0073e9SAndroid Build Coastguard Worker  ham
110*da0073e9SAndroid Build Coastguard Worker  bar
111*da0073e9SAndroid Build Coastguard Worker  end
112*da0073e9SAndroid Build Coastguard Worker  ```
113*da0073e9SAndroid Build Coastguard Worker* `CHECK-SOURCE-HIGHLIGHTED: <pattern>`
114*da0073e9SAndroid Build Coastguard Worker  Check for highlighted source ranges. This is useful when writing tests regarding generated error messages that require source code highlighting.
115*da0073e9SAndroid Build Coastguard Worker  For example the following pattern:
116*da0073e9SAndroid Build Coastguard Worker  ```
117*da0073e9SAndroid Build Coastguard Worker  # CHECK-SOURCE-HIGHLIGHTED: raise Exception("raised exception
118*da0073e9SAndroid Build Coastguard Worker  ```
119*da0073e9SAndroid Build Coastguard Worker  would match the following input:
120*da0073e9SAndroid Build Coastguard Worker  ```
121*da0073e9SAndroid Build Coastguard Worker  def method_that_raises() -> torch.Tensor:
122*da0073e9SAndroid Build Coastguard Worker      raise Exception("raised exception")  # noqa: TRY002
123*da0073e9SAndroid Build Coastguard Worker      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ <--- HERE
124*da0073e9SAndroid Build Coastguard Worker  builtins.Exception: raised exception
125*da0073e9SAndroid Build Coastguard Worker  ```
126*da0073e9SAndroid Build Coastguard Worker* `CHECK-REGEX: <pattern>`
127*da0073e9SAndroid Build Coastguard Worker  Scans the input until `PATTERN` is matched, accepts RE syntax for std::regex.
128