Skip to content

Commit beb4d6c

Browse files
committed
[circt-test] Allow tests to filter according to test runners
Interpret the `require_runners` and `exclude_runners` attributes on formal tests as a filter for the potential runners that can execute a test. If the set of required runners is not empty, a runner from this set is picked. Tests for which no runner is available report as "unsupported", for example if none of the available runners match the filter.
1 parent 5cb9a9f commit beb4d6c

File tree

4 files changed

+212
-33
lines changed

4 files changed

+212
-33
lines changed

include/circt/Dialect/Verif/VerifOps.td

+27
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,33 @@ def FormalOp : VerifOp<"formal", [
304304
verif.assert %4 : i1
305305
}
306306
```
307+
308+
### Parameters
309+
The following parameters have a predefined meaning and are interpreted by
310+
tools such as `circt-test` to guide execution of tests:
311+
312+
- `ignore`: Indicates whether the test should be ignored and skipped. This
313+
can be useful for temporarily disabling tests without having to remove
314+
them from the input. Must be a _boolean_ value.
315+
```mlir
316+
verif.formal @Foo {ignore = true}
317+
```
318+
319+
- `require_runners`: A list of test runners that may be used to execute this
320+
test. This option may be used to force a test to run using one of a few
321+
known-good runners, acting like a whitelist. Must be an _array_ of
322+
_strings_.
323+
```mlir
324+
verif.formal @Foo {require_runners = ["sby", "circt-bmc"]}
325+
```
326+
327+
- `exclude_runners`: A list of test runners that must not be used to execute
328+
this test. This option may be used to exclude a few known-bad runners from
329+
executing this test, acting like a blacklist. Must be an _array_ of
330+
_strings_.
331+
```mlir
332+
verif.formal @Foo {exclude_runners = ["sby", "circt-bmc"]}
333+
```
307334
}];
308335
let arguments = (ins
309336
SymbolNameAttr:$sym_name,

integration_test/circt-test/basic.mlir

+31-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// See other `basic-*.mlir` files for run lines.
22
// RUN: true
33

4-
// CHECK: 1 tests FAILED, 6 passed, 1 ignored
4+
// CHECK: 1 tests FAILED, 9 passed, 1 ignored, 3 unsupported
55

66
hw.module @FullAdder(in %a: i1, in %b: i1, in %ci: i1, out s: i1, out co: i1) {
77
%0 = comb.xor %a, %b : i1
@@ -149,3 +149,33 @@ verif.formal @ALUFailure {depth = 3} {
149149
%ne = comb.icmp ne %z0, %z1 : i4
150150
verif.assert %ne : i1
151151
}
152+
153+
verif.formal @RunnerRequireEither {require_runners = ["sby", "circt-bmc"]} {
154+
%0 = hw.constant true
155+
verif.assert %0 : i1
156+
}
157+
158+
verif.formal @RunnerRequireSby {require_runners = ["sby"]} {
159+
%0 = hw.constant true
160+
verif.assert %0 : i1
161+
}
162+
163+
verif.formal @RunnerRequireCirctBmc {require_runners = ["circt-bmc"]} {
164+
%0 = hw.constant true
165+
verif.assert %0 : i1
166+
}
167+
168+
verif.formal @RunnerExcludeEither {exclude_runners = ["sby", "circt-bmc"]} {
169+
%0 = hw.constant true
170+
verif.assert %0 : i1
171+
}
172+
173+
verif.formal @RunnerExcludeSby {exclude_runners = ["sby"]} {
174+
%0 = hw.constant true
175+
verif.assert %0 : i1
176+
}
177+
178+
verif.formal @RunnerExcludeCirctBmc {exclude_runners = ["circt-bmc"]} {
179+
%0 = hw.constant true
180+
verif.assert %0 : i1
181+
}

test/circt-test/errors.mlir

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// RUN: circt-test --verify-diagnostics --split-input-file %s
2+
3+
// expected-error @below {{`ignore` attribute of test "Foo" must be a boolean}}
4+
verif.formal @Foo {ignore = "hello"} {}
5+
6+
// -----
7+
// expected-error @below {{`require_runners` attribute of test "Foo" must be an array}}
8+
verif.formal @Foo {require_runners = "hello"} {}
9+
10+
// -----
11+
// expected-error @below {{`exclude_runners` attribute of test "Foo" must be an array}}
12+
verif.formal @Foo {exclude_runners = "hello"} {}
13+
14+
// -----
15+
// expected-error @below {{element of `require_runners` array of test "Foo" must be a string}}
16+
verif.formal @Foo {require_runners = [42]} {}
17+
18+
// -----
19+
// expected-error @below {{element of `exclude_runners` array of test "Foo" must be a string}}
20+
verif.formal @Foo {exclude_runners = [42]} {}

tools/circt-test/circt-test.cpp

+134-32
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
#include "mlir/Parser/Parser.h"
3535
#include "mlir/Pass/PassManager.h"
3636
#include "mlir/Support/FileUtilities.h"
37+
#include "mlir/Support/ToolUtilities.h"
3738
#include "llvm/ADT/ScopeExit.h"
3839
#include "llvm/Support/CommandLine.h"
3940
#include "llvm/Support/FileSystem.h"
@@ -89,6 +90,18 @@ struct Options {
8990
cl::list<std::string> runners{"r", cl::desc("Use a specific set of runners"),
9091
cl::value_desc("name"),
9192
cl::MiscFlags::CommaSeparated, cl::cat(cat)};
93+
94+
cl::opt<bool> verifyDiagnostics{
95+
"verify-diagnostics",
96+
cl::desc("Check that emitted diagnostics match expected-* lines on the "
97+
"corresponding line"),
98+
cl::init(false), cl::Hidden, cl::cat(cat)};
99+
100+
cl::opt<bool> splitInputFile{
101+
"split-input-file",
102+
cl::desc("Split the input file into pieces and process each "
103+
"chunk independently"),
104+
cl::init(false), cl::Hidden, cl::cat(cat)};
92105
};
93106
Options opts;
94107

@@ -105,7 +118,7 @@ class Runner {
105118
/// The name of the runner. The user can filter runners by this name, and
106119
/// individual tests can indicate that they can or cannot run with runners
107120
/// based on this name.
108-
std::string name;
121+
StringAttr name;
109122
/// The runner binary. The value of this field is resolved using
110123
/// `findProgramByName` and stored in `binaryPath`.
111124
std::string binary;
@@ -140,14 +153,14 @@ void RunnerSuite::addDefaultRunners() {
140153
{
141154
// SymbiYosys
142155
Runner runner;
143-
runner.name = "sby";
156+
runner.name = StringAttr::get(context, "sby");
144157
runner.binary = "circt-test-runner-sby.py";
145158
runners.push_back(std::move(runner));
146159
}
147160
{
148161
// circt-bmc
149162
Runner runner;
150-
runner.name = "circt-bmc";
163+
runner.name = StringAttr::get(context, "circt-bmc");
151164
runner.binary = "circt-test-runner-circt-bmc.py";
152165
runner.readsMLIR = true;
153166
runners.push_back(std::move(runner));
@@ -209,9 +222,15 @@ class Test {
209222
/// be the location of an MLIR op, or a line in some other source file.
210223
LocationAttr loc;
211224
/// Whether or not the test should be ignored
212-
bool ignore;
225+
bool ignore = false;
213226
/// The user-defined attributes of this test.
214227
DictionaryAttr attrs;
228+
/// The set of runners that can execute this test, specified by the
229+
/// "require_runners" array attribute in `attrs`.
230+
SmallPtrSet<StringAttr, 1> requiredRunners;
231+
/// The set of runners that should be skipped for this test, specified by the
232+
/// "exclude_runners" array attribute in `attrs`.
233+
SmallPtrSet<StringAttr, 1> excludedRunners;
215234
};
216235

217236
/// A collection of tests discovered in some MLIR input.
@@ -227,7 +246,7 @@ class TestSuite {
227246

228247
TestSuite(MLIRContext *context, bool listIgnored)
229248
: context(context), listIgnored(listIgnored) {}
230-
void discoverInModule(ModuleOp module);
249+
LogicalResult discoverInModule(ModuleOp module);
231250
};
232251
} // namespace
233252

@@ -240,20 +259,63 @@ static StringRef toString(TestKind kind) {
240259
return "unknown";
241260
}
242261

262+
static LogicalResult
263+
collectRunnerFilters(DictionaryAttr attrs, StringRef attrName,
264+
llvm::SmallPtrSetImpl<StringAttr> &names, Location loc,
265+
StringAttr testName) {
266+
auto attr = attrs.get(attrName);
267+
if (!attr)
268+
return success();
269+
270+
auto arrayAttr = dyn_cast<ArrayAttr>(attr);
271+
if (!arrayAttr)
272+
return mlir::emitError(loc) << "`" << attrName << "` attribute of test "
273+
<< testName << " must be an array";
274+
275+
for (auto elementAttr : arrayAttr.getValue()) {
276+
auto stringAttr = dyn_cast<StringAttr>(elementAttr);
277+
if (!stringAttr)
278+
return mlir::emitError(loc)
279+
<< "element of `" << attrName << "` array of test " << testName
280+
<< " must be a string; got " << elementAttr;
281+
names.insert(stringAttr);
282+
}
283+
284+
return success();
285+
}
286+
243287
/// Discover all tests in an MLIR module.
244-
void TestSuite::discoverInModule(ModuleOp module) {
245-
module.walk([&](verif::FormalOp op) {
288+
LogicalResult TestSuite::discoverInModule(ModuleOp module) {
289+
auto result = module.walk([&](verif::FormalOp op) {
246290
Test test;
247291
test.name = op.getSymNameAttr();
248292
test.kind = TestKind::Formal;
249293
test.loc = op.getLoc();
250294
test.attrs = op.getParametersAttr();
251-
if (auto boolAttr = test.attrs.getAs<BoolAttr>("ignore"))
295+
296+
// Handle the `ignore` attribute.
297+
if (auto attr = test.attrs.get("ignore")) {
298+
auto boolAttr = dyn_cast<BoolAttr>(attr);
299+
if (!boolAttr) {
300+
op.emitError() << "`ignore` attribute of test " << test.name
301+
<< " must be a boolean";
302+
return WalkResult::interrupt();
303+
}
252304
test.ignore = boolAttr.getValue();
253-
else
254-
test.ignore = false;
305+
}
306+
307+
// Handle the `require_runners` and `exclude_runners` attributes.
308+
if (failed(collectRunnerFilters(test.attrs, "require_runners",
309+
test.requiredRunners, test.loc,
310+
test.name)) ||
311+
failed(collectRunnerFilters(test.attrs, "exclude_runners",
312+
test.excludedRunners, test.loc, test.name)))
313+
return WalkResult::interrupt();
314+
255315
tests.push_back(std::move(test));
316+
return WalkResult::advance();
256317
});
318+
return failure(result.wasInterrupted());
257319
}
258320

259321
//===----------------------------------------------------------------------===//
@@ -272,7 +334,7 @@ static LogicalResult listRunners(RunnerSuite &suite) {
272334

273335
for (auto &runner : suite.runners) {
274336
auto &os = output->os();
275-
os << runner.name;
337+
os << runner.name.getValue();
276338
if (runner.ignore)
277339
os << " ignored";
278340
else if (runner.available)
@@ -336,31 +398,20 @@ static LogicalResult listTests(TestSuite &suite) {
336398
return success();
337399
}
338400

339-
/// Entry point for the circt-test tool. At this point an MLIRContext is
340-
/// available, all dialects have been registered, and all command line options
341-
/// have been parsed.
342-
static LogicalResult execute(MLIRContext *context) {
343-
SourceMgr srcMgr;
344-
SourceMgrDiagnosticHandler handler(srcMgr, context);
345-
346-
// Discover all available test runners.
347-
RunnerSuite runnerSuite(context);
348-
runnerSuite.addDefaultRunners();
349-
if (failed(runnerSuite.resolve()))
350-
return failure();
351-
352-
// List all runners and exit if requested.
353-
if (opts.listRunners)
354-
return listRunners(runnerSuite);
355-
356-
// Parse the input file.
357-
auto module = parseSourceFile<ModuleOp>(opts.inputFilename, srcMgr, context);
401+
/// Called once the suite of available runners has been determined and a module
402+
/// has been parsed. If the `--split-input-file` option is set, this function is
403+
/// called once for each split of the input file.
404+
static LogicalResult executeWithHandler(MLIRContext *context,
405+
RunnerSuite &runnerSuite,
406+
SourceMgr &srcMgr) {
407+
auto module = parseSourceFile<ModuleOp>(srcMgr, context);
358408
if (!module)
359409
return failure();
360410

361411
// Discover all tests in the input.
362412
TestSuite suite(context, opts.listIgnored);
363-
suite.discoverInModule(*module);
413+
if (failed(suite.discoverInModule(*module)))
414+
return failure();
364415
if (suite.tests.empty()) {
365416
llvm::errs() << "no tests discovered\n";
366417
return success();
@@ -417,12 +468,16 @@ static LogicalResult execute(MLIRContext *context) {
417468
for (auto &candidate : runnerSuite.runners) {
418469
if (candidate.ignore || !candidate.available)
419470
continue;
471+
if (!test.requiredRunners.empty() &&
472+
!test.requiredRunners.contains(candidate.name))
473+
continue;
474+
if (test.excludedRunners.contains(candidate.name))
475+
continue;
420476
runner = &candidate;
421477
break;
422478
}
423479
if (!runner) {
424480
++numUnsupported;
425-
mlir::emitError(test.loc) << "no runner for test " << test.name;
426481
return;
427482
}
428483

@@ -490,6 +545,7 @@ static LogicalResult execute(MLIRContext *context) {
490545
auto d = mlir::emitError(test.loc)
491546
<< "test " << test.name.getValue() << " failed";
492547
d.attachNote() << "see `" << logPath << "`";
548+
d.attachNote() << "executed with " << runner->name.getValue();
493549
} else {
494550
++numPassed;
495551
}
@@ -517,6 +573,52 @@ static LogicalResult execute(MLIRContext *context) {
517573
return success(numFailed == 0);
518574
}
519575

576+
/// Entry point for the circt-test tool. At this point an MLIRContext is
577+
/// available, all dialects have been registered, and all command line options
578+
/// have been parsed.
579+
static LogicalResult execute(MLIRContext *context) {
580+
// Discover all available test runners.
581+
RunnerSuite runnerSuite(context);
582+
runnerSuite.addDefaultRunners();
583+
if (failed(runnerSuite.resolve()))
584+
return failure();
585+
586+
// List all runners and exit if requested.
587+
if (opts.listRunners)
588+
return listRunners(runnerSuite);
589+
590+
// Read the input file.
591+
auto input = llvm::MemoryBuffer::getFileOrSTDIN(opts.inputFilename);
592+
if (input.getError()) {
593+
WithColor::error() << "could not open input file " << opts.inputFilename;
594+
return failure();
595+
}
596+
597+
// Process the input file. If requested by the user, split the input file and
598+
// process each chunk separately. This is useful for verifying diagnostics.
599+
auto processBuffer = [&](std::unique_ptr<llvm::MemoryBuffer> chunk,
600+
raw_ostream &) {
601+
SourceMgr srcMgr;
602+
srcMgr.AddNewSourceBuffer(std::move(chunk), llvm::SMLoc());
603+
604+
// Call `executeWithHandler` with either the regular diagnostic handler, or,
605+
// if `--verify-diagnostics` is set, with the verifying handler.
606+
if (opts.verifyDiagnostics) {
607+
SourceMgrDiagnosticVerifierHandler handler(srcMgr, context);
608+
context->printOpOnDiagnostic(false);
609+
(void)executeWithHandler(context, runnerSuite, srcMgr);
610+
return handler.verify();
611+
}
612+
613+
SourceMgrDiagnosticHandler handler(srcMgr, context);
614+
return executeWithHandler(context, runnerSuite, srcMgr);
615+
};
616+
617+
return mlir::splitAndProcessBuffer(
618+
std::move(*input), processBuffer, llvm::outs(),
619+
opts.splitInputFile ? mlir::kDefaultSplitMarker : "");
620+
}
621+
520622
int main(int argc, char **argv) {
521623
InitLLVM y(argc, argv);
522624

0 commit comments

Comments
 (0)