Skip to content

Commit 3a2ae68

Browse files
committed
feat: addind a basic test suite
1 parent 3eb0e52 commit 3a2ae68

97 files changed

Lines changed: 5461 additions & 13 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/test.yml

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
name: Run Grammar Tests
2+
3+
on:
4+
push:
5+
branches: ['*']
6+
pull_request:
7+
branches: [develop, main]
8+
9+
jobs:
10+
test-javascript:
11+
runs-on: ubuntu-latest
12+
defaults:
13+
run:
14+
working-directory: js
15+
steps:
16+
- uses: actions/checkout@v4
17+
18+
- uses: actions/setup-node@v4
19+
with:
20+
node-version: 20
21+
cache: 'npm'
22+
cache-dependency-path: js/package-lock.json
23+
24+
- name: Install ANTLR4
25+
run: cd .. && make dev
26+
27+
- name: Build grammar
28+
run: npm run build-grammar
29+
30+
- name: Install dependencies
31+
run: npm install
32+
33+
- name: Run tests
34+
run: npm test
35+
36+
test-python:
37+
runs-on: ubuntu-latest
38+
defaults:
39+
run:
40+
working-directory: python
41+
steps:
42+
- uses: actions/checkout@v4
43+
44+
- uses: actions/setup-python@v5
45+
with:
46+
python-version: '3.11'
47+
48+
- name: Install ANTLR4
49+
run: cd .. && make dev
50+
51+
- name: Build grammar
52+
run: cd .. && make python_parser
53+
54+
- name: Install dependencies
55+
run: |
56+
pip install antlr4-python3-runtime==4.13.2
57+
pip install -r requirements-dev.txt
58+
59+
- name: Run tests
60+
run: pytest test_grammar.py -v
61+
62+
test-java:
63+
runs-on: ubuntu-latest
64+
defaults:
65+
run:
66+
working-directory: java
67+
steps:
68+
- uses: actions/checkout@v4
69+
70+
- uses: actions/setup-java@v4
71+
with:
72+
distribution: 'temurin'
73+
java-version: '21'
74+
75+
- name: Install ANTLR4
76+
run: cd .. && make dev
77+
78+
- name: Cache Maven packages
79+
uses: actions/cache@v4
80+
with:
81+
path: ~/.m2
82+
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
83+
restore-keys: ${{ runner.os }}-m2
84+
85+
- name: Run tests
86+
run: mvn test

.gitignore

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,53 @@
1-
# VSCode, Eclipse, and IntelliJ IDEA project files
2-
.vscode
3-
.settings
1+
# OS files
2+
.DS_Store
3+
Thumbs.db
4+
5+
# IDEs and editors
6+
.vscode/
7+
.settings/
48
.classpath
59
.project
6-
.idea
10+
.idea/
11+
*.iml
12+
*.swp
13+
*.swo
14+
*~
15+
16+
# Java
17+
target/
18+
*.class
19+
*.jar
20+
*.war
21+
.antlr/
722

8-
# Python virtual environments
9-
env
10-
build
11-
python/uvl/__pycache__
23+
# Python
24+
__pycache__/
25+
*.py[cod]
26+
*$py.class
27+
*.so
28+
.Python
29+
env/
30+
venv/
31+
.venv/
32+
build/
33+
dist/
34+
*.egg-info/
35+
.eggs/
36+
.pytest_cache/
37+
.coverage
38+
htmlcov/
1239
python/uvl/*
1340
!python/uvl/UVLCustomLexer.py
1441

15-
# Node.js
16-
js/node_modules
42+
# JavaScript
43+
node_modules/
44+
js/node_modules/
1745
js/package-lock.json
46+
js/src/lib/
47+
*.log
48+
npm-debug.log*
1849

19-
# Java build files
20-
.antlr/
21-
target/
50+
# Environment files
51+
.env
52+
.env.local
53+
.env.*.local

java/pom.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,12 @@
6969
<artifactId>antlr4-runtime</artifactId>
7070
<version>${antlr4.version}</version>
7171
</dependency>
72+
<dependency>
73+
<groupId>org.junit.jupiter</groupId>
74+
<artifactId>junit-jupiter</artifactId>
75+
<version>5.10.2</version>
76+
<scope>test</scope>
77+
</dependency>
7278
</dependencies>
7379

7480
<distributionManagement>
@@ -202,6 +208,12 @@
202208
</execution>
203209
</executions>
204210
</plugin>
211+
212+
<plugin>
213+
<groupId>org.apache.maven.plugins</groupId>
214+
<artifactId>maven-surefire-plugin</artifactId>
215+
<version>3.2.5</version>
216+
</plugin>
205217
</plugins>
206218
</build>
207219

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package uvl;
2+
3+
import org.antlr.v4.runtime.BaseErrorListener;
4+
import org.antlr.v4.runtime.RecognitionException;
5+
import org.antlr.v4.runtime.Recognizer;
6+
7+
public class ThrowingErrorListener extends BaseErrorListener {
8+
public static final ThrowingErrorListener INSTANCE = new ThrowingErrorListener();
9+
10+
@Override
11+
public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol,
12+
int line, int charPositionInLine, String msg,
13+
RecognitionException e) {
14+
throw new RuntimeException("Line " + line + ":" + charPositionInLine + " " + msg);
15+
}
16+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package uvl;
2+
3+
import org.antlr.v4.runtime.CharStream;
4+
import org.antlr.v4.runtime.CharStreams;
5+
import org.antlr.v4.runtime.CommonTokenStream;
6+
import org.junit.jupiter.params.ParameterizedTest;
7+
import org.junit.jupiter.params.provider.MethodSource;
8+
9+
import java.io.IOException;
10+
import java.nio.file.Files;
11+
import java.nio.file.Path;
12+
import java.nio.file.Paths;
13+
import java.util.stream.Stream;
14+
15+
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
16+
import static org.junit.jupiter.api.Assertions.assertThrows;
17+
18+
class UVLParserTest {
19+
20+
private static final Path TEST_MODELS_DIR = Paths.get("../test_models");
21+
22+
static Stream<Path> validModels() throws IOException {
23+
return Files.walk(TEST_MODELS_DIR)
24+
.filter(p -> p.toString().endsWith(".uvl"))
25+
.filter(p -> !p.toString().contains("faulty"));
26+
}
27+
28+
static Stream<Path> faultyModels() throws IOException {
29+
return Files.list(TEST_MODELS_DIR.resolve("faulty"))
30+
.filter(p -> p.toString().endsWith(".uvl"));
31+
}
32+
33+
private void parseFile(Path file) throws IOException {
34+
CharStream input = CharStreams.fromPath(file);
35+
UVLJavaLexer lexer = new UVLJavaLexer(input);
36+
lexer.removeErrorListeners();
37+
lexer.addErrorListener(ThrowingErrorListener.INSTANCE);
38+
CommonTokenStream tokens = new CommonTokenStream(lexer);
39+
UVLJavaParser parser = new UVLJavaParser(tokens);
40+
parser.removeErrorListeners();
41+
parser.addErrorListener(ThrowingErrorListener.INSTANCE);
42+
parser.featureModel();
43+
}
44+
45+
@ParameterizedTest(name = "{0}")
46+
@MethodSource("validModels")
47+
void testValidModel(Path file) {
48+
assertDoesNotThrow(() -> parseFile(file), "Failed to parse: " + file);
49+
}
50+
51+
@ParameterizedTest(name = "{0}")
52+
@MethodSource("faultyModels")
53+
void testFaultyModel(Path file) {
54+
assertThrows(RuntimeException.class, () -> parseFile(file), "Expected parse error for: " + file);
55+
}
56+
}

js/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"@eslint/js": "^9.8.0",
1313
"@rollup/plugin-node-resolve": "^15.2.3",
1414
"eslint": "^9.8.0",
15+
"glob": "^10.3.10",
1516
"globals": "^15.8.0",
1617
"vitest": "^2.0.4"
1718
},

js/test/grammar.test.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { expect, describe, test } from 'vitest';
2+
import { FeatureModel } from "../index.js";
3+
import { glob } from 'glob';
4+
import path from 'path';
5+
6+
const TEST_MODELS_DIR = path.resolve(import.meta.dirname, '../../test_models');
7+
8+
const validFiles = glob.sync(`${TEST_MODELS_DIR}/**/*.uvl`, { ignore: '**/faulty/**' });
9+
const faultyFiles = glob.sync(`${TEST_MODELS_DIR}/faulty/*.uvl`);
10+
11+
describe('Valid UVL models', () => {
12+
validFiles.forEach(file => {
13+
const relativePath = path.relative(TEST_MODELS_DIR, file);
14+
test(`parses ${relativePath}`, () => {
15+
expect(() => new FeatureModel(file)).not.toThrow();
16+
});
17+
});
18+
});
19+
20+
describe('Faulty UVL models', () => {
21+
faultyFiles.forEach(file => {
22+
const relativePath = path.relative(TEST_MODELS_DIR, file);
23+
test(`rejects ${relativePath}`, () => {
24+
expect(() => new FeatureModel(file)).toThrow();
25+
});
26+
});
27+
});

python/requirements-dev.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pytest>=7.0.0

python/test_grammar.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import pytest
2+
import glob
3+
import os
4+
5+
from main import get_tree
6+
7+
TEST_MODELS_DIR = os.path.join(os.path.dirname(__file__), "..", "test_models")
8+
9+
10+
def get_valid_files():
11+
all_files = glob.glob(os.path.join(TEST_MODELS_DIR, "**", "*.uvl"), recursive=True)
12+
return [f for f in all_files if os.sep + "faulty" + os.sep not in f]
13+
14+
15+
def get_faulty_files():
16+
return glob.glob(os.path.join(TEST_MODELS_DIR, "faulty", "*.uvl"))
17+
18+
19+
def relative_path(file):
20+
return os.path.relpath(file, TEST_MODELS_DIR)
21+
22+
23+
@pytest.mark.parametrize("uvl_file", get_valid_files(), ids=relative_path)
24+
def test_valid_model(uvl_file):
25+
tree = get_tree(uvl_file)
26+
assert tree is not None
27+
28+
29+
@pytest.mark.parametrize("uvl_file", get_faulty_files(), ids=relative_path)
30+
def test_faulty_model(uvl_file):
31+
with pytest.raises(Exception):
32+
get_tree(uvl_file)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
features
2+
A {Price 1}
3+
optional
4+
B {Price 2}
5+
C {Price 5}
6+
7+
constraints
8+
sum(Price) > 2
9+
avg(Price) > 1

0 commit comments

Comments
 (0)