Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support parsing only specific unittests (e.g. @betterc-test) #357

Merged
merged 2 commits into from
Aug 14, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions posix.mak
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,11 @@ $(ROOT)/tests_extractor: tests_extractor.d
################################################################################

test_tests_extractor: $(ROOT)/tests_extractor
$< -i ./test/tests_extractor/ascii.d | diff - ./test/tests_extractor/ascii.d.ext
$< -i ./test/tests_extractor/iteration.d | diff - ./test/tests_extractor/iteration.d.ext
for file in ascii iteration ; do \
$< -i "./test/tests_extractor/$${file}.d" | diff -p - "./test/tests_extractor/$${file}.d.ext"; \
done
$< -a betterc -i "./test/tests_extractor/attributes.d" | diff -p - "./test/tests_extractor/attributes.d.ext";
$< --betterC -i "./test/tests_extractor/betterc.d" | diff -p - "./test/tests_extractor/betterc.d.ext";

RDMD_TEST_COMPILERS = $(DMD)
RDMD_TEST_EXECUTABLE = $(ROOT)/rdmd
Expand Down
44 changes: 44 additions & 0 deletions test/tests_extractor/attributes.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
module attributes;

enum betterc;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the name should be betterC

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just a test to check whether the attribute filter works. It could be any attribute. See the -a betterc line in posix.mak


@betterc @safe @("foo") unittest
{
assert(1 == 1);
}

@safe @("foo") unittest
{
assert(2 == 2);
}

///
@("foo") unittest
{
assert(3 == 3);
}

@("foo") @betterc unittest
{
assert(4 == 4);
Copy link
Contributor

@JinShil JinShil Jul 29, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that this has a @betterc attribute, shoudn't the body be something like this?

version(D_BetterC)
{
    assert(4 == 4);
}
else
{
    assert(0);
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. The idea here is just to selectively filter out unittests. Such rewrites would just complicate the job of this extraction tool and mess up the line matching.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But I don't see the test proving that the extraction tool only extracts those test with the correct attribute, as all tests in this file succeed. Though, I also don't understand the purpose of the .ext file either.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, now I get it. Ok it's good!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the record: the .ext files are the result of applying the extraction script on this file.

}

@("betterc") @([1, 2, 3]) unittest
{
assert(5 == 5);
}

@nogc @("foo", "betterc", "bar") @safe unittest
{
assert(6 == 6);
}

@nogc @("foo", "better", "bar") @safe unittest
{
assert(7 == 7);
}

@("betterd") unittest
{
assert(8 == 8);
}
32 changes: 32 additions & 0 deletions test/tests_extractor/attributes.d.ext
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# line 3
unittest
{
import attributes;

assert(1 == 1);
}

# line 19
unittest
{
import attributes;

assert(4 == 4);
}

# line 24
unittest
{
import attributes;

assert(5 == 5);
}

# line 29
unittest
{
import attributes;

assert(6 == 6);
}

16 changes: 16 additions & 0 deletions test/tests_extractor/betterc.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module betterc;

///
unittest
{
int a = 1;
assert(a == 2);
}

///
unittest
{
int b = 2;
assert(b == 2);
assert(b == 3);
}
24 changes: 24 additions & 0 deletions test/tests_extractor/betterc.d.ext
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# line 2
unittest
{
import betterc;

int a = 1;
assert(a == 2);
}

# line 9
unittest
{
import betterc;

int b = 2;
assert(b == 2);
assert(b == 3);
}

extern(C) void main()
{
static foreach(u; __traits(getUnitTests, __traits(parent, main)))
u();
}
86 changes: 75 additions & 11 deletions tests_extractor.d
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ dependency "libdparse" version="~>0.8.0"
* Parses all public unittests that are visible on dlang.org
* (= annotated with three slashes)
*
* Copyright (C) 2017 by D Language Foundation
* Copyright (C) 2018 by D Language Foundation
*
* Author: Sebastian Wilzbach
*
Expand All @@ -33,11 +33,13 @@ class TestVisitor : ASTVisitor
File outFile;
ubyte[] sourceCode;
string moduleName;
VisitorConfig config;

this(File outFile, ubyte[] sourceCode)
this(File outFile, ubyte[] sourceCode, VisitorConfig config)
{
this.outFile = outFile;
this.sourceCode = sourceCode;
this.config = config;
}

alias visit = ASTVisitor.visit;
Expand All @@ -54,17 +56,67 @@ class TestVisitor : ASTVisitor
moduleName = outFile.name.replace(".d", "").replace(dirSeparator, ".").replace(".package", "");
}
m.accept(this);
// -betterC doesn't run unittests out of the box
if (config.betterCOutput)
{
outFile.writeln(q{extern(C) void main()
{
static foreach(u; __traits(getUnitTests, __traits(parent, main)))
u();
}});
}
}

override void visit(const Declaration decl)
{
if (decl.unittest_ !is null && hasDdocHeader(sourceCode, decl))
if (decl.unittest_ !is null && shouldIncludeUnittest(decl))
print(decl.unittest_);

decl.accept(this);
}

private:

bool shouldIncludeUnittest(const Declaration decl)
{
if (!config.attributes.empty)
return filterForUDAs(decl);
else
return hasDdocHeader(sourceCode, decl);
}

bool filterForUDAs(const Declaration decl)
{
foreach (attr; decl.attributes)
{
if (attr.atAttribute is null)
continue;

// check for @myArg
if (config.attributes.canFind(attr.atAttribute.identifier.text))
return true;

// support @("myArg") too
if (auto argList = attr.atAttribute.argumentList)
{
foreach (arg; argList.items)
{
if (auto unaryExp = cast(UnaryExpression) arg)
if (auto primaryExp = unaryExp.primaryExpression)
{
auto attribute = primaryExp.primary.text;
if (attribute.length >= 2)
{
attribute = attribute[1 .. $ - 1];
if (config.attributes.canFind(attribute))
return true;
}
}
}
}
}
return false;
}
void print(const Unittest u)
{
/*
Expand Down Expand Up @@ -93,7 +145,7 @@ private:
}
}

void parseFile(File inFile, File outFile)
void parseFile(File inFile, File outFile, VisitorConfig visitorConfig)
{
import dparse.lexer;
import dparse.parser : parseModule;
Expand All @@ -111,11 +163,11 @@ void parseFile(File inFile, File outFile)

RollbackAllocator rba;
auto m = parseModule(tokens.array, inFile.name, &rba);
auto visitor = new TestVisitor(outFile, sourceCode);
auto visitor = new TestVisitor(outFile, sourceCode, visitorConfig);
visitor.visit(m);
}

void parseFileDir(string inputDir, string fileName, string outputDir)
void parseFileDir(string inputDir, string fileName, string outputDir, VisitorConfig visitorConfig)
{
import std.path : buildPath, dirSeparator, buildNormalizedPath;

Expand All @@ -132,7 +184,13 @@ void parseFileDir(string inputDir, string fileName, string outputDir)
// convert the file path to a nice output file, e.g. std/uni.d -> std_uni.d
string outName = fileNameNormalized.replace(dirSeparator, "_");

parseFile(File(fileName), File(buildPath(outputDir, outName), "w"));
parseFile(File(fileName), File(buildPath(outputDir, outName), "w"), visitorConfig);
}

struct VisitorConfig
{
string[] attributes; /// List of attributes to extract;
bool betterCOutput; /// Add custom extern(C) main method for running D's unittests
}

void main(string[] args)
Expand All @@ -143,12 +201,17 @@ void main(string[] args)
string inputDir;
string outputDir = "./out";
string ignoredFilesStr;
string modulePrefix = "";
string modulePrefix;
string attributesStr;
VisitorConfig visitorConfig;

auto helpInfo = getopt(args, config.required,
"inputdir|i", "Folder to start the recursive search for unittest blocks (can be a single file)", &inputDir,
"outputdir|o", "Folder to which the extracted test files should be saved (stdout for a single file)", &outputDir,
"ignore", "Comma-separated list of files to exclude (partial matching is supported)", &ignoredFilesStr);
"ignore", "Comma-separated list of files to exclude (partial matching is supported)", &ignoredFilesStr,
"attributes|a", "Comma-separated list of UDAs that the unittest should have", &attributesStr,
"betterC", "Add custom extern(C) main method for running D's unittests", &visitorConfig.betterCOutput,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it's better to rename this to --main as it's now more analogous to the -main switch of DMD?

);

if (helpInfo.helpWanted)
{
Expand All @@ -162,6 +225,7 @@ to in the output directory.

inputDir = inputDir.asNormalizedPath.array;
Algebraic!(string, File) outputLocation = cast(string) outputDir.asNormalizedPath.array;
visitorConfig.attributes = attributesStr.split(",");

if (!exists(outputDir))
mkdir(outputDir);
Expand Down Expand Up @@ -196,8 +260,8 @@ to in the output directory.
{
stderr.writeln("parsing ", file);
outputLocation.visit!(
(string outputFolder) => parseFileDir(inputDir, file, outputFolder),
(File outputFile) => parseFile(File(file.name, "r"), outputFile),
(string outputFolder) => parseFileDir(inputDir, file, outputFolder, visitorConfig),
(File outputFile) => parseFile(File(file.name, "r"), outputFile, visitorConfig),
);
}
else
Expand Down