Skip to content
This repository was archived by the owner on Sep 28, 2022. It is now read-only.

Commit 6eb4c71

Browse files
committed
feat(testing): Init of the testing module
Add a CLI Add a Gradle integration Signed-off-by: Daniel Petisme <[email protected]>
1 parent 1c669b9 commit 6eb4c71

File tree

8 files changed

+295
-19
lines changed

8 files changed

+295
-19
lines changed

build.gradle

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ repositories {
3232
ext {
3333
goloCliMain = 'org.eclipse.golo.cli.Main'
3434
goloSources = fileTree('src/main/golo').include('**/*.golo')
35+
goloTests = fileTree('src/test/golo').include('**/*.golo')
3536
goloDocs = file("$buildDir/docs/golodoc")
3637
}
3738

@@ -107,10 +108,21 @@ test {
107108

108109
// .................................................................................................................. //
109110

111+
task golotest(type: JavaExec, dependsOn: [testClasses]) {
112+
main = goloCliMain
113+
args = ['test', '--files'] + goloTests
114+
classpath = sourceSets.main.runtimeClasspath
115+
inputs.files goloTests
116+
description = 'Run Golo Tests'
117+
group = 'Test'
118+
}
119+
120+
// .................................................................................................................. //
121+
110122
processResources {
111123
filter(org.apache.tools.ant.filters.ReplaceTokens, tokens: [
112-
version: version,
113-
timestamp: versioning.info.full
124+
version: version,
125+
timestamp: versioning.info.full
114126
])
115127
}
116128

@@ -169,7 +181,7 @@ task vanillaScripts(type: CreateStartScripts) {
169181

170182
mainClassName = goloCliMain
171183
applicationDefaultJvmArgs = [
172-
'-Xms256m', '-Xmx1024M', '-Xss1024M', '-server', '-XX:-TieredCompilation', '-XX:+AggressiveOpts'
184+
'-Xms256m', '-Xmx1024M', '-Xss1024M', '-server', '-XX:-TieredCompilation', '-XX:+AggressiveOpts'
173185
]
174186

175187
startScripts.dependsOn vanillaScripts
@@ -211,29 +223,29 @@ distributions {
211223
// .................................................................................................................. //
212224

213225
ext.mavenRepos = [
214-
[
215-
name: 'project',
216-
url: "$buildDir/maven-repo",
217-
user: '',
218-
password: ''
219-
]
226+
[
227+
name: 'project',
228+
url: "$buildDir/maven-repo",
229+
user: '',
230+
password: ''
231+
]
220232
]
221233

222234
if (project.hasProperty('eclipseRepoUsername')) {
223235
mavenRepos.add([
224-
name: 'eclipse',
225-
url : 'https://repo.eclipse.org/content/repositories/golo-' + (version.endsWith('-SNAPSHOT') ? 'snapshots/' : 'releases/'),
226-
user: eclipseRepoUsername,
227-
password: eclipseRepoPassword
236+
name: 'eclipse',
237+
url : 'https://repo.eclipse.org/content/repositories/golo-' + (version.endsWith('-SNAPSHOT') ? 'snapshots/' : 'releases/'),
238+
user: eclipseRepoUsername,
239+
password: eclipseRepoPassword
228240
])
229241
}
230242

231243
if (project.hasProperty('bintrayRepoUsername')) {
232244
mavenRepos.add([
233-
name: 'bintray',
234-
url: 'https://api.bintray.com/maven/golo-lang/golo-lang/golo',
235-
user: bintrayRepoUsername,
236-
password: bintrayRepoPassword
245+
name: 'bintray',
246+
url: 'https://api.bintray.com/maven/golo-lang/golo-lang/golo',
247+
user: bintrayRepoUsername,
248+
password: bintrayRepoPassword
237249
])
238250
}
239251

@@ -331,4 +343,4 @@ task wrapper(type: Wrapper) {
331343
description 'Generates the Gradle wrapper scripts.'
332344
}
333345

334-
// .................................................................................................................. //
346+
// .................................................................................................................. //

src/main/golo/testing.golo

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
module gololang.Testing
2+
3+
import gololang.testing.Runner
4+
import gololang.testing.Suite
5+
import gololang.testing.Test
6+
7+
augment gololang.testing.Runner.types.Runner {
8+
9+
function describe = |this, description, fn| {
10+
let parent = this: currentSuite()
11+
let suite = Suite(
12+
description,
13+
parent
14+
)
15+
parent: add(suite)
16+
this: currentSuite(suite)
17+
fn()
18+
this: currentSuite(parent)
19+
}
20+
21+
function it = |this, description, fn| {
22+
let parent = this: currentSuite()
23+
parent: add(Test(
24+
description,
25+
fn,
26+
parent
27+
))
28+
}
29+
30+
function beforeEach = |runner, fn| -> runner: currentSuite(): addBeforeEach(fn)
31+
function afterEach = |runner, fn| -> runner: currentSuite(): addAfterEach(fn)
32+
}

src/main/golo/testing/runner.golo

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
module gololang.testing.Runner
2+
3+
import gololang.testing.Suite
4+
import gololang.testing.Test
5+
6+
struct Runner = {
7+
currentSuite
8+
}
9+
10+
11+
function build = {
12+
let top_level_suite = Suite("TOP_LEVEL_SUITE", null)
13+
return Runner(top_level_suite)
14+
}
15+
16+
augment gololang.testing.Runner.types.Runner {
17+
function run = |this| -> this: currentSuite(): run()
18+
}

src/main/golo/testing/suite.golo

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
module gololang.testing.Suite
2+
3+
import gololang.testing.Test
4+
5+
struct Suite = {
6+
description,
7+
parent,
8+
children,
9+
befores,
10+
afters
11+
}
12+
13+
function Suite = |description, parent| -> gololang.testing.Suite.types.Suite(description, parent, list[], list[], list[])
14+
15+
augment gololang.testing.Suite.types.Suite {
16+
17+
function run = |this| {
18+
this: children(): each(|test| {
19+
this: befores(): each(|before| -> before())
20+
test: run()
21+
this: afters(): each(|after| -> after())
22+
})
23+
}
24+
25+
function add = |this, it| {
26+
it: parent(this)
27+
this: children(): add(it)
28+
}
29+
30+
function addBeforeEach = |this, before| -> this: befores(): add(before)
31+
function addAfterEach = |this, after| -> this: afters(): add(after)
32+
}

src/main/golo/testing/test.golo

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
module gololang.testing.Test
2+
3+
struct Test = {
4+
description,
5+
fn,
6+
parent
7+
}
8+
9+
augment gololang.testing.Test.types.Test {
10+
function run = |this| {
11+
this: fn()()
12+
}
13+
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package org.eclipse.golo.cli.command;
2+
3+
import com.beust.jcommander.Parameter;
4+
import com.beust.jcommander.Parameters;
5+
import org.eclipse.golo.cli.command.spi.CliCommand;
6+
import org.eclipse.golo.compiler.GoloClassLoader;
7+
import org.eclipse.golo.compiler.GoloCompilationException;
8+
9+
import java.io.File;
10+
import java.io.FileInputStream;
11+
import java.io.IOException;
12+
import java.lang.invoke.MethodHandle;
13+
import java.lang.invoke.MethodType;
14+
import java.lang.reflect.Method;
15+
import java.net.URLClassLoader;
16+
import java.nio.file.Files;
17+
import java.nio.file.Path;
18+
import java.nio.file.Paths;
19+
import java.util.*;
20+
import java.util.function.Consumer;
21+
import java.util.stream.Stream;
22+
23+
import static java.lang.invoke.MethodHandles.lookup;
24+
import static java.lang.invoke.MethodType.genericMethodType;
25+
26+
@Parameters(commandNames = {"test"}, commandDescription = "Run golo tests")
27+
public class TestCommand implements CliCommand {
28+
29+
public static final String TEST_METHOD_NAME = "spec";
30+
public static final MethodType TEST_METHOD_TYPE = genericMethodType(1);
31+
public static final String BUILD_METHOD_NAME = "build";
32+
33+
@Parameter(names = "--files", variableArity = true, description = "Test files (*.golo and directories)", required = true)
34+
List<String> files = new LinkedList<>();
35+
36+
@Parameter(names = "--classpath", variableArity = true, description = "Classpath elements (.jar and directories)")
37+
List<String> classpath = new LinkedList<>();
38+
39+
private final GoloClassLoader loader;
40+
41+
public TestCommand() throws Throwable {
42+
URLClassLoader primaryClassLoader = primaryClassLoader(this.classpath);
43+
Thread.currentThread().setContextClassLoader(primaryClassLoader);
44+
this.loader = new GoloClassLoader(primaryClassLoader);
45+
}
46+
47+
private Object runner() throws Throwable {
48+
Class runnerClass = Class.forName("gololang.testing.Runner");
49+
//TODO replace the search with the exact signature when the Runner struct will be stabilized
50+
Method m = Arrays.asList(runnerClass.getDeclaredMethods()).stream().filter(method -> method.getName().equals(BUILD_METHOD_NAME)).findFirst().get();
51+
MethodHandle mh = lookup().unreflect(m);
52+
return mh.invokeExact();
53+
}
54+
55+
@Override
56+
public void execute() throws Throwable {
57+
Object runner = runner();
58+
Consumer<Class> loadSpecification = clazz -> loadSpecification(runner, clazz);
59+
files.stream()
60+
.map(Paths::get)
61+
.flatMap(this::treeFiles)
62+
.map(this::pathToClass)
63+
.forEach(loadSpecification);
64+
run(runner);
65+
}
66+
67+
private Stream<Path> treeFiles(Path path) {
68+
return listFiles(path).flatMap(it ->
69+
it.toFile().isDirectory() ?
70+
treeFiles(path) :
71+
Stream.of(it)
72+
);
73+
}
74+
75+
private Stream<Path> listFiles(Path path) {
76+
if (path.toFile().isDirectory()) {
77+
try {
78+
return Files.list(path).filter(testFile -> testFile.toString().endsWith(".golo"));
79+
} catch (IOException e) {
80+
System.out.println(e.getMessage());
81+
}
82+
}
83+
return Stream.of(path);
84+
}
85+
86+
private void run(Object runner) throws Throwable {
87+
Class augmentions = Class.forName("gololang.testing.Runner$gololang$testing$Runner$types$Runner");
88+
MethodHandle run = lookup().findStatic(augmentions, "run", genericMethodType(1));
89+
run.invoke(runner);
90+
}
91+
92+
//TODO refactor with CLICommand file loader
93+
private Class<?> pathToClass(Path filepath) {
94+
File file = filepath.toFile();
95+
try (FileInputStream in = new FileInputStream(file)) {
96+
return loader.load(file.getName(), in);
97+
} catch (GoloCompilationException e) {
98+
handleCompilationException(e);
99+
} catch (IOException e) {
100+
e.printStackTrace();
101+
}
102+
return null;
103+
}
104+
105+
//TODO refactor with CLICommand#callRun method
106+
private void loadSpecification(Object runner, Class<?> klass) {
107+
MethodHandle mh = null;
108+
try {
109+
mh = lookup().findStatic(klass, TEST_METHOD_NAME, TEST_METHOD_TYPE);
110+
} catch (NoSuchMethodException e) {
111+
System.out.println(e.getMessage());
112+
} catch (IllegalAccessException e) {
113+
System.out.println(e.getMessage());
114+
}
115+
if (mh != null) {
116+
try {
117+
mh.invoke(runner);
118+
} catch (Throwable throwable) {
119+
throwable.printStackTrace();
120+
}
121+
}
122+
}
123+
}

src/main/resources/META-INF/services/org.eclipse.golo.cli.command.spi.CliCommand

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@ org.eclipse.golo.cli.command.DiagnoseCommand
1212
org.eclipse.golo.cli.command.DocCommand
1313
org.eclipse.golo.cli.command.GoloGoloCommand
1414
org.eclipse.golo.cli.command.InitCommand
15+
org.eclipse.golo.cli.command.TestCommand
1516
org.eclipse.golo.cli.command.RunCommand
16-
org.eclipse.golo.cli.command.VersionCommand
17+
org.eclipse.golo.cli.command.VersionCommand

src/test/golo/testingSpec.golo

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
module gololang.TestingSpec
2+
3+
import gololang.Testing
4+
import gololang.testing.Runner
5+
import gololang.testing.Suite
6+
import gololang.testing.Test
7+
8+
9+
function dummyTest = |fn| -> gololang.testing.Test.Test("dummyTest", fn, null)
10+
function dummySuite = -> gololang.testing.Suite.Suite("dummySuite", null)
11+
12+
function spec = |$| {
13+
14+
$: describe("A suite", {
15+
16+
$: beforeEach({
17+
println("Before each")
18+
})
19+
20+
$: it("should set a test parent to this when added", {
21+
let test = dummyTest({})
22+
let suite = dummySuite()
23+
require(suite: children(): isEmpty(), "The suite should be empty")
24+
suite: add(test)
25+
require(suite: children(): size() == 1, "The test should be added")
26+
})
27+
28+
$: it("should run beforeEach before every single test", {
29+
let myList = list[]
30+
let before = { myList: add("before") }
31+
let test1 = dummyTest({myList: add("test1")})
32+
let test2 = dummyTest({myList: add("test2")})
33+
let suite = dummySuite()
34+
suite: add(test1)
35+
suite: add(test2)
36+
suite: addBeforeEach(before)
37+
suite: run()
38+
require(myList: join("-") == "before-test1-before-test2", "the befores should be before the tests")
39+
})
40+
41+
$: afterEach({
42+
println("After each")
43+
})
44+
})
45+
}

0 commit comments

Comments
 (0)