[circt-test] Add test discovery classes and `-l` option (#7755)

Add the `TestSuite` and `Test` classes to represent the discovered
tests. Instead of directly printing ops to the output, first traverse
the provided MLIR module and populate a test suite object with the
discovered tests. Then print the list of tests in the test suite.

A separate test discovery step will later allow us to discover tests via
other means, for example, by scanning an entire Verilog directory for
marker comments.
This commit is contained in:
Fabian Schuiki 2024-10-31 09:41:15 -07:00 committed by GitHub
parent c5d16ee194
commit f2fbf63152
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 106 additions and 41 deletions

View File

@ -1,6 +1,6 @@
// RUN: circt-test %s | FileCheck %s
// RUN: circt-test %s --json | FileCheck --check-prefix=JSON %s
// RUN: circt-as %s -o - | circt-test | FileCheck %s
// RUN: circt-test -l %s | FileCheck %s
// RUN: circt-test -l --json %s | FileCheck --check-prefix=JSON %s
// RUN: circt-as %s -o - | circt-test -l | FileCheck %s
// JSON: [

View File

@ -50,11 +50,17 @@ namespace {
/// The tool's command line options.
struct Options {
cl::OptionCategory cat{"circt-test Options"};
cl::opt<std::string> inputFilename{cl::Positional, cl::desc("<input file>"),
cl::init("-"), cl::cat(cat)};
cl::opt<std::string> outputFilename{
"o", cl::desc("Output filename (`-` for stdout)"),
cl::value_desc("filename"), cl::init("-"), cl::cat(cat)};
cl::opt<bool> listTests{"l", cl::desc("List tests in the input and exit"),
cl::init(false), cl::cat(cat)};
cl::opt<bool> json{"json", cl::desc("Emit test list as JSON array"),
cl::init(false), cl::cat(cat)};
};
@ -62,46 +68,104 @@ Options opts;
} // namespace
//===----------------------------------------------------------------------===//
// Test Discovery
//===----------------------------------------------------------------------===//
namespace {
/// The various kinds of test that can be executed.
enum class TestKind { Formal };
/// A single discovered test.
class Test {
public:
/// The name of the test. This is also the name of the top-level module passed
/// to the formal or simulation tool to be run.
StringAttr name;
/// The kind of test, such as "formal" or "simulation".
TestKind kind;
/// An optional location indicating where this test was discovered. This can
/// be the location of an MLIR op, or a line in some other source file.
LocationAttr loc;
/// The user-defined attributes of this test.
DictionaryAttr attrs;
};
/// A collection of tests discovered in some MLIR input.
class TestSuite {
public:
/// The MLIR context that is used to intern attributes and where any MLIR
/// tests were discovered.
MLIRContext *context;
/// The tests discovered in the input.
std::vector<Test> tests;
TestSuite(MLIRContext *context) : context(context) {}
void discoverInModule(ModuleOp module);
};
} // namespace
/// Convert a `TestKind` to a string representation.
static StringRef toString(TestKind kind) {
switch (kind) {
case TestKind::Formal:
return "formal";
}
return "unknown";
}
/// Discover all tests in an MLIR module.
void TestSuite::discoverInModule(ModuleOp module) {
module.walk([&](verif::FormalOp op) {
Test test;
test.name = op.getSymNameAttr();
test.kind = TestKind::Formal;
test.loc = op.getLoc();
test.attrs = op->getDiscardableAttrDictionary();
tests.push_back(std::move(test));
});
}
//===----------------------------------------------------------------------===//
// Tool Implementation
//===----------------------------------------------------------------------===//
/// List all the tests in a given module.
static LogicalResult listTests(ModuleOp module, llvm::raw_ostream &output) {
static LogicalResult listTests(TestSuite &suite) {
// Open the output file for writing.
std::string errorMessage;
auto output = openOutputFile(opts.outputFilename, &errorMessage);
if (!output)
return emitError(UnknownLoc::get(suite.context)) << errorMessage;
// Handle JSON output.
if (opts.json) {
json::OStream json(output, 2);
json::OStream json(output->os(), 2);
json.arrayBegin();
auto result = module.walk([&](Operation *op) {
if (auto formalOp = dyn_cast<verif::FormalOp>(op)) {
json.objectBegin();
auto guard = make_scope_exit([&] { json.objectEnd(); });
json.attribute("name", formalOp.getSymName());
json.attribute("kind", "formal");
auto attrs = formalOp->getDiscardableAttrDictionary();
if (!attrs.empty()) {
json.attributeBegin("attrs");
auto guard = make_scope_exit([&] { json.attributeEnd(); });
if (failed(convertAttributeToJSON(json, attrs))) {
op->emitError() << "unsupported attributes: `" << attrs
<< "` cannot be converted to JSON";
return WalkResult::interrupt();
}
}
auto guard = make_scope_exit([&] { json.arrayEnd(); });
for (auto &test : suite.tests) {
json.objectBegin();
auto guard = make_scope_exit([&] { json.objectEnd(); });
json.attribute("name", test.name.getValue());
json.attribute("kind", toString(test.kind));
if (!test.attrs.empty()) {
json.attributeBegin("attrs");
auto guard = make_scope_exit([&] { json.attributeEnd(); });
if (failed(convertAttributeToJSON(json, test.attrs)))
return mlir::emitError(test.loc)
<< "unsupported attributes: `" << test.attrs
<< "` cannot be converted to JSON";
}
return WalkResult::advance();
});
json.arrayEnd();
return failure(result.wasInterrupted());
}
output->keep();
return success();
}
// Handle regular text output.
module.walk([&](Operation *op) {
if (auto formalOp = dyn_cast<verif::FormalOp>(op)) {
output << formalOp.getSymName() << " formal"
<< " " << formalOp->getDiscardableAttrDictionary() << "\n";
}
});
for (auto &test : suite.tests)
output->os() << test.name.getValue() << " " << toString(test.kind) << " "
<< test.attrs << "\n";
output->keep();
return success();
}
@ -112,22 +176,23 @@ static LogicalResult execute(MLIRContext *context) {
SourceMgr srcMgr;
SourceMgrDiagnosticHandler handler(srcMgr, context);
// Open the output file for writing.
std::string errorMessage;
auto output = openOutputFile(opts.outputFilename, &errorMessage);
if (!output)
return emitError(UnknownLoc::get(context)) << errorMessage;
// Parse the input file.
auto module = parseSourceFile<ModuleOp>(opts.inputFilename, srcMgr, context);
if (!module)
return failure();
// List all tests in the input.
if (failed(listTests(*module, output->os())))
return failure();
// Discover all tests in the input.
TestSuite suite(context);
suite.discoverInModule(*module);
if (suite.tests.empty()) {
llvm::errs() << "no tests discovered\n";
return success();
}
// List all tests in the input and exit if requested.
if (opts.listTests)
return listTests(suite);
output->keep();
return success();
}