|
27 | 27 | * Be careful in deviating from this pattern, but do so at your own risk, so long as `pass()` and |
28 | 28 | * `fail()` hold as in the example above. |
29 | 29 | */ |
30 | | - |
31 | | -private import codeql.util.Unit |
32 | | -private import qtil.strings.Plural |
33 | | -private import qtil.inheritance.UnderlyingString |
34 | | -import QnitImpl::Public |
35 | | - |
36 | | -private module QnitImpl { |
37 | | - module Public { |
38 | | - /** |
39 | | - * A string that is re-typedef'd to Qnit for the sake of a pretty API. |
40 | | - * |
41 | | - * This class is used to define the predicate `run(Qnit test)`, which is prettier than |
42 | | - * `run(string test)`. See also the `pass()` and `fail()` methods. |
43 | | - */ |
44 | | - bindingset[this] |
45 | | - class Qnit extends UnderlyingString { |
46 | | - bindingset[this] |
47 | | - predicate isFailing() { str().matches("FAILURE: %") } |
48 | | - |
49 | | - bindingset[this] |
50 | | - predicate isPassing() { str().matches("PASS: %") } |
51 | | - |
52 | | - /** |
53 | | - * Call this method inside of `Test.run(Qnit test)` to report a failing test case. |
54 | | - * |
55 | | - * It is recommended to use unique strings for each test case, as this will allow you to |
56 | | - * uniquely identify which tests failed, due to the way QL works. |
57 | | - */ |
58 | | - bindingset[description] |
59 | | - predicate fail(string description) { this = "FAILURE: " + description } |
60 | | - |
61 | | - /** |
62 | | - * Call this method inside of `Test.run(Qnit test)` to report a passing test case. |
63 | | - * |
64 | | - * It is recommended to use unique strings for each test case, as this will allow Qnit to |
65 | | - * properly count the number of tests that passed, due to the way QL works. |
66 | | - */ |
67 | | - bindingset[name] |
68 | | - predicate pass(string name) { this = "PASS: " + name } |
69 | | - } |
70 | | - |
71 | | - /** |
72 | | - * A test case that can be run by Qnit. |
73 | | - * |
74 | | - * This class is used to define the predicate `run(Qnit test)`, which is used to run the test |
75 | | - * case. The `pass()` and `fail()` methods are used to report the result of the test case, as |
76 | | - * follows: |
77 | | - * |
78 | | - * ```ql |
79 | | - * class MyTest extends Test, Case { |
80 | | - * override predicate run(Qnit test) { |
81 | | - * if (someCondition) |
82 | | - * then test.pass("some condition passed") |
83 | | - * else test.fail("some condition failed") |
84 | | - * } |
85 | | - * } |
86 | | - * ``` |
87 | | - * |
88 | | - * This is simply an abstract class extending the CodeQL's empty `Unit` type, as its subclasses |
89 | | - * are singletons with no underlying data. |
90 | | - */ |
91 | | - abstract class Case extends Unit { |
92 | | - /** |
93 | | - * Overridable method to define the behavior of the test case, which should generally follow |
94 | | - * the pattern of: |
95 | | - * |
96 | | - * ```ql |
97 | | - * if (someCondition) |
98 | | - * then test.pass("some condition passed") |
99 | | - * else test.fail("some condition failed") |
100 | | - * ``` |
101 | | - * |
102 | | - * It is best to use `pass()` and `fail()` with unique strings, as this will allow Qnit to |
103 | | - * properly count the number of tests that passed, and uniquely identify which tests failed, |
104 | | - * due to the way QL works. |
105 | | - * |
106 | | - * This is designed this way because we cannot execute an abstract method in QL while knowing |
107 | | - * which concrete class it belongs to. Rather, `Case.run(x)` holds for all `x` defined in all |
108 | | - * test cases. Making `x` a field does not solve this problem. We also must work around the |
109 | | - * limitation of not having anonymous functions. Etc. |
110 | | - */ |
111 | | - abstract predicate run(Qnit test); |
112 | | - } |
113 | | - |
114 | | - /** |
115 | | - * Extend this class to suppress a warning that is generated by QL when you override an |
116 | | - * abstract class without implementing a characteristic predicate. |
117 | | - * |
118 | | - * ```ql |
119 | | - * class MyTest extends Test, Case { |
120 | | - * ... |
121 | | - * } |
122 | | - * ``` |
123 | | - * |
124 | | - * Extends CodeQL's `Unit` type to match the `Test` class and support multiple inheritance. |
125 | | - */ |
126 | | - class Test extends Unit { } |
127 | | - |
128 | | - query predicate test(string report) { |
129 | | - if count(Qnit test | isFailing(test)) = 0 |
130 | | - then |
131 | | - exists(int passed | |
132 | | - passed = count(Qnit test | isPassing(test)) and |
133 | | - report = plural("1 test", "All " + passed + " tests", passed) + " passed." |
134 | | - ) |
135 | | - else |
136 | | - exists(Qnit test | |
137 | | - (isFailing(test) or isPassing(test)) and |
138 | | - report = test |
139 | | - ) |
140 | | - } |
141 | | - } |
142 | | - |
143 | | - /** |
144 | | - * A base class that defines `toString()` in order to enable the API `extends Test, Case`. |
145 | | - * |
146 | | - * Extends CodeQL's `Unit` type becaue it has no underlying data. |
147 | | - * |
148 | | - * Ordinarily, the QL compiler will issue a warning if you override an abstract class without |
149 | | - * implementing a characteristic predicate, as in `class MyTest extends Case {...}`. In order to |
150 | | - * suppress this warning, we can extend an additional non-abstract class, and therefore our |
151 | | - * recommended pattern is to extend `Test, Case` in order to define a test case. |
152 | | - * |
153 | | - * In QL, `Test` and `Case` must both extend the same base type. If that base type were `Unit`, |
154 | | - * then both `Test` and `Case` would need to implement `toString()`. At this point, QL would |
155 | | - * issue a warning for `class MyTest extends Test, Case {...}` because QL cannot disambiguate |
156 | | - * which `toString()` method to inherit. Therefore, we define `CaseBase` as a subclass of `TCase` |
157 | | - * which defines an unambiguous `toString()` method. |
158 | | - * |
159 | | - * With this workaround, we can define `class MyTest extends Test, Case {...}` without any |
160 | | - * warnings from the QL compiler, and without having to implement `toString()` in every test. |
161 | | - */ |
162 | | - private predicate isFailing(Qnit test) { exists(Case c | c.run(test) and test.isFailing()) } |
163 | | - |
164 | | - private predicate isPassing(Qnit test) { exists(Case c | c.run(test) and test.isPassing()) } |
165 | | -} |
| 30 | +import qtil.testing.impl.Qnit |
| 31 | +import qtil.testing.impl.Test |
| 32 | +import qtil.testing.impl.Runner |
0 commit comments