Skip to content

Commit a721b4f

Browse files
authored
Add Runner for Integration Tests for the SOM language (#128)
2 parents 77ece77 + 6b11663 commit a721b4f

22 files changed

+1663
-17
lines changed

.github/workflows/ci.yml

Lines changed: 57 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,30 @@ name: Tests
33
on: [push, pull_request]
44

55
jobs:
6+
python-style:
7+
name: Python Checks
8+
runs-on: ubuntu-24.04
9+
steps:
10+
- name: Checkout Repository
11+
uses: actions/checkout@v4
12+
13+
- name: Install Black, PyLint and PyTest
14+
run: |
15+
python -m pip install --upgrade pip
16+
pip install black pylint pytest
17+
18+
- name: Run Black Check
19+
run: |
20+
black ./IntegrationTests --check --diff
21+
22+
- name: Run PyLint
23+
run: |
24+
pylint ./IntegrationTests
25+
26+
- name: Run PyTest
27+
run: |
28+
pytest -m tester ./IntegrationTests
29+
630
test_soms:
731
runs-on: ubuntu-24.04 # ubuntu-latest
832
continue-on-error: ${{ matrix.not-up-to-date }}
@@ -34,7 +58,12 @@ jobs:
3458

3559
- name: PySOM
3660
repo: PySOM.git
37-
som: "PYTHON=python SOM_INTERP=BC ./som.sh"
61+
som: ./som.sh
62+
build: |
63+
export PYTHON=python
64+
export SOM_INTERP=BC
65+
echo "PYTHON=python" >> "$GITHUB_ENV"
66+
echo "SOM_INTERP=BC" >> "$GITHUB_ENV"
3867
not-up-to-date: false
3968

4069
- name: SOM (Java)
@@ -47,6 +76,7 @@ jobs:
4776
repo: TruffleSOM.git
4877
build: |
4978
export JAVA_HOME=$JAVA_HOME_17_X64
79+
echo "JAVA_HOME=$JAVA_HOME_17_X64" >> "$GITHUB_ENV"
5080
./som --setup mx
5181
export PATH=$PATH:`pwd`/../mx
5282
./som --setup labsjdk
@@ -55,7 +85,8 @@ jobs:
5585
./som --setup labsjdk
5686
mx build
5787
58-
som: "JAVA_HOME=$JAVA_HOME_17_X64 ./som -G"
88+
som: ./som -G
89+
test-expectations: integration-tests-ast.yml
5990
not-up-to-date: false
6091

6192
- name: Specification Tests
@@ -66,8 +97,7 @@ jobs:
6697
repo: som-rs.git
6798
build: "cargo build --release -p som-interpreter-bc"
6899
som: "./target/release/som-interpreter-bc"
69-
som-tests: " -c ../Smalltalk ../TestSuite -- TestHarness"
70-
not-up-to-date: false
100+
not-up-to-date: true
71101

72102
# - name: ykSOM
73103
# repo: yksom.git
@@ -149,14 +179,17 @@ jobs:
149179
cd som-vm
150180
git --no-pager log -n 1
151181
152-
echo Build ${{ matrix.repo }}
153-
${{ matrix.build }}
182+
if [ ! -z "${{ matrix.build }}" ]
183+
then
184+
echo Build ${{ matrix.repo }}
185+
${{ matrix.build }}
186+
fi
154187
155188
- name: Run Tests on SOM VM
156189
if: ${{ matrix.som != 'spec' }}
157190
run: |
158191
cd som-vm
159-
if [ "${{ matrix.som-tests }}" == "" ]
192+
if [ -z "${{ matrix.som-tests }}" ]
160193
then
161194
# The default settings for running the test harness, supported by most SOM VMs
162195
export SOM_TESTS="-cp ../Smalltalk ../TestSuite/TestHarness.som"
@@ -167,6 +200,23 @@ jobs:
167200
echo "${{ matrix.som }} $SOM_TESTS"
168201
eval "${{ matrix.som }} $SOM_TESTS"
169202
203+
- name: Run Integration Tests
204+
if: ${{ matrix.som != 'spec' }}
205+
run: |
206+
python -m pip install --upgrade pip
207+
pip install pytest
208+
export VM="som-vm/${{ matrix.som }}"
209+
export CLASSPATH=Smalltalk
210+
211+
if [ -z "${{ matrix.test-expectations }}" ]; then
212+
export TEST_EXPECTATIONS=som-vm/integration-tests.yml
213+
else
214+
export TEST_EXPECTATIONS=som-vm/${{ matrix.test-expectations }}
215+
fi
216+
217+
export AWFY=Examples/AreWeFastYet/Core
218+
pytest IntegrationTests
219+
170220
# We currently test SomSom only on TruffleSOM
171221
- name: Test SomSom on TruffleSOM
172222
if: ${{ matrix.repo == 'TruffleSOM.git' }}

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
Smalltalk/SOMCore.csp
2-
2+
IntegrationTests/__pycache__
3+
IntegrationTests/.idea

IntegrationTests/README.md

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
# SOM Integration Tests
2+
3+
These tests are end-to-end tests, allowing us to check elements that go
4+
beyond the language. For instance, we can test the parser, how fatal
5+
errors are handled, and how the VM interacts with different libraries
6+
on the classpath.
7+
8+
Most of the tests come from lang_tests of
9+
[yksom](https://github.com/softdevteam/yksom/tree/master/lang_tests).
10+
11+
## 1. Getting Started: Running the Integration Tests
12+
13+
The tests can be run using pytest by running `pytest`.
14+
15+
However, to run successfully, we need to set the following
16+
environment variables. The paths have to be set relative to the
17+
current working directory, which is where the tests are run from.
18+
19+
- `VM`: the path to the SOM executable
20+
- `CLASSPATH`: the claspath, e.g., `./core-lib/Smalltalk`
21+
- `AWFY`: the classpath for tests the use the AreWeFastYet (AWFY)
22+
library, e.g., `core-lib/Examples/AreWeFastYet/Core`
23+
24+
Example run:
25+
26+
```bash
27+
export VM=./som.sh
28+
export CLASSPATH=./core-lib/Smalltalk
29+
export AWFY=./core-lib/Examples/AreWeFastYet/Core
30+
python3 -m pytest
31+
```
32+
33+
At the time of writing, August 2025, most SOM implementations are
34+
not supporting all tests, because the yksom test for behavior that
35+
has not been specified yet.
36+
37+
To successfully run the tests as regression tests, most SOM
38+
implementations come with a `TEST_EXPECTIONS` file that defines,
39+
which tests are expected to fail.
40+
41+
A full example would look like this:
42+
43+
```bash
44+
export VM=./som.sh
45+
export CLASSPATH=./core-lib/Smalltalk
46+
export AWFY=./core-lib/Examples/AreWeFastYet/Core
47+
TEST_EXPECTATIONS=integration-tests.yml python3 -m pytest
48+
```
49+
50+
51+
## 2. Writing Tests and Expectation Files
52+
53+
### 2.1 Writing a Test
54+
55+
The integration tests are valid `.som` files that can be run by any
56+
SOM VM.
57+
58+
The expected results of the tests are specified in a comment at the
59+
top of the file, which looks like this:
60+
61+
```
62+
"
63+
VM:
64+
status: error
65+
custom_classpath: $AWFY:example/classpath:$CLASSPATH
66+
case_sensitive: False
67+
stdout:
68+
1000
69+
...
70+
2 is an integer
71+
stderr:
72+
...
73+
ERROR MESSAGE
74+
"
75+
```
76+
77+
The expected results are specified in a YAML-like format, which
78+
supports the following keys:
79+
80+
- `status`: the expected exit code of the VM, which can be an integer,
81+
`success` or `error`
82+
83+
- `custom_classpath`: a custom classpath to be used for this test,
84+
which can include environment variables like `$AWFY` or `$CLASSPATH`.
85+
If a required environment variable is not set, the test will be
86+
marked as failure, reporting the missing variable.
87+
88+
- `case_sensitive`: can be `true` or `false` to specify whether the
89+
output should be checked case-sensitively or not. If not specified,
90+
it defaults to `false`.
91+
92+
- `stdout`: one or more lines that are the expected output of the
93+
test on standard output. Each line can contain the special
94+
characters `...` or `***` to specify gaps or partial word matching.
95+
See below for more details on how to use these.
96+
97+
- `stderr`: one or more lines that are the expected output of the
98+
test on standard error. As for `stdout`, each line can contain
99+
the special characters `...` or `***`.
100+
101+
### 2.2 Omissions and Partial Matching in Tests
102+
103+
Tests may not want to check for each precise bit of output.
104+
By using `...`, omissions can be made that will not be checked.
105+
Specifically, `...` can be used to indicate a gap, which may contain
106+
zero or more characters, in the actual output.
107+
108+
Similarly, `***` can be used as part of a "word" or number.
109+
If the output contains more characters than before the `***`,
110+
the output has to match exactly the characters after the `***`.
111+
112+
However, currently, `***` cannot be used in the same line as `...`.
113+
114+
#### 2.2.1 Omissions with "..."
115+
116+
The following are a few examples of how to use `...` in tests.
117+
118+
##### Example 1: Omitting a line
119+
120+
Expectation defined in the test:
121+
122+
```yaml
123+
stdout:
124+
Hello, World
125+
...
126+
Goodbye
127+
```
128+
129+
This would correctly accept the following two outputs.
130+
131+
Output 1:
132+
133+
```
134+
Hello, World
135+
Today is a Monday
136+
Goodbye
137+
```
138+
139+
Output 2:
140+
141+
```
142+
Hello, World
143+
Goodbye
144+
```
145+
146+
###### Example 2: Omitting words
147+
148+
It can also be used in more elaborate ways, for instance, when
149+
omitting words. However, the given words still most be present to
150+
pass the test.
151+
152+
```yaml
153+
stdout:
154+
Hello, ... sample ...
155+
... is ... this line
156+
... little ...
157+
```
158+
159+
This would accept the following output:
160+
161+
```
162+
Hello, this is some sample output
163+
There is some more on this line
164+
And a little more here
165+
```
166+
167+
#### 2.2.2 Partial Matching with "***"
168+
169+
To partially match a word or number, but be able to accept additional
170+
*precision*, we can use `***` in the expected output.
171+
172+
A number or word is any connected string of alphanumeric characters.
173+
Using `***` in the number/word means:
174+
175+
1. All numbers/characters before `***` must be present.
176+
2. The characters after `***` do not have to be present, but if they
177+
are present, they must match exactly.
178+
3. There cannot be more characters than specified.
179+
180+
This is particularly useful to testing the output of floating point
181+
calculations. Different languages have different default levels of
182+
precision, which makes floating point output platform-specific.
183+
184+
Let's assume the following expected output in a test:
185+
186+
```yaml
187+
stdout:
188+
1.111***123
189+
```
190+
191+
This will accept any of the following outputs:
192+
193+
- `1.111`
194+
- `1.1111`
195+
- `1.11112`
196+
- `1.111123`
197+
198+
However, it will not accept the following:
199+
200+
- `1.1`
201+
- `1.11`
202+
- `1.111124`
203+
- `1.1111234`
204+
205+
206+
### 2.3 Expectation Files
207+
208+
Expectation files are YAML files that can be used to mark tests as
209+
`known_failures`, `failing_as_unspecified`, `unsupported` or
210+
`do_not_run`.
211+
212+
To help SOM implementers to get started, an expectation file can be
213+
generated based on the test results obtained during a run.
214+
215+
By setting the `GENERATE_EXPECTATIONS_FILE` environment variable.
216+
Setting this environment variable will also print out how many tests
217+
passed, which tests passed that were not expected to and which tests
218+
failed.
219+
220+
Tests are identified by their path relative to the test runner,
221+
i.e., from within the `IntegrationTests` directory.
222+
223+
A minimal valid file could look like this:
224+
225+
```yaml
226+
known_failures:
227+
- Tests/test.som
228+
```
229+
230+
The maining of the different keys is as follows:
231+
232+
- `known_failures`: these tests are expected to fail
233+
- `failing_as_unspecified`: these tests are expected to fail, but
234+
SOM is assumed to not yet specify the expected behaviour.
235+
- `unsupported`: these tests are expected to fail because the
236+
relevant SOM implementation does not intend to support this
237+
feature.
238+
- `do_not_run`: these tests will not be run, and it is not known
239+
whether they will fail or not.
240+
241+
## 3. Developing the test_runner
242+
243+
To run the tests of the `test_runner` itself, run `pytest -m tester`.
244+
This will set the marker expression to `tester`, which prevents the
245+
tests from being deselected. By default, we do not run them and only
246+
run the SOM tests.
247+
248+
```bash
249+
pytest -m tester
250+
```

IntegrationTests/Tests/load_file.som

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
22
VM:
33
status: success
44
stdout:
5-
...
6-
hello_world1 = (
7-
run = ( 'Hello world' println )
5+
ClassB = ClassA (
6+
| c d |
7+
----
8+
| c4 c5 c6 |
89
)
910
"
1011

1112
load_file = (
1213
run = (
13-
(system loadFile: 'core-lib/IntegrationTests/Tests/hello_world1.som') println.
14+
(system loadFile: 'TestSuite/ClassB.som') println.
1415
)
1516
)

0 commit comments

Comments
 (0)