This is a separate repository for maintaining and running tests for Speech Rule Engine.
The majority of tests is available in a JSON format.
{
"factory": "Mandatory",
"information": "Optional but recommended",
"name": "Optional, necessary for analytics only",
"base": "Optional filename",
"active": "Optional filename for test output",
"exclude": [],
"_commentX_": "Optional",
... other test class specific entries
"tests": "ALL" |
{
"test1": {
"test": true|false,
"input": "Optional",
"expected": "Mandatory test output"
... other test specific fields
}
... More uniquely named tests
}
}
Field | Required | Usage |
---|---|---|
factory |
Mandatory | Specifies the test class factory entry that is used for this test. |
information |
Recommended | Information on the test used as description for JEST testsuites. |
See below for description conventions | ||
name |
Optional | Name of the testsuite. This is necessary for analytics only, |
Analytics files are use this name as prefix and only tests | ||
that contain a name field will be compiled for analytics. |
||
Consequently, names should be unique. See below for conventions. | ||
base |
Optional | Filename of the input file if this is input test. |
This is either an absolute path or relative to the input folder. | ||
active |
Optional | Target filename for tests that produce output. |
It is used to compile output tests and does not have to be unique. | ||
This is either an absolute path or relative to the ouptut folder. | ||
exclude |
Optional | List of tests to be excluded. Useful if loading from base files. |
tests |
Mandatory | "ALL" or Association list of named tests. |
In case of an input test "ALL" specifies that all tests from |
||
the base file should be run for the given test class. | ||
... |
Optional | Other fields needed to intialise the test class. |
Field | Required | Usage |
---|---|---|
test |
Optional | Boolean specifying if the test should be run. Default is true |
input |
Mandatory | Input for the tests. |
expected |
Optional | The expected output. Can be omitted, for example, in the case |
of tests that compare output for two methods. | ||
... |
Optional | Other fields that are needed to run a test. |
We distinguish input tests and expected only tests. The latter contain all
the information for the test in the single JSON object, while the former load
input information from a common base
file, generally given in the input
directory.
The structure of base
files is similar to the expected
files. expected
can suppress running of certain tests using the excluded
field. Fields
from the expected file overwrite those of the input file.
The non-JEST test runner outputs warnings in case input tests have no expected value and are not explicitly excluded.
name
: Names are used as prefixes for output in the analytics module. As the output structure is flat, they should be unique. For speech rule tests they should be of the form${Locale}${Domain}${Type}
and camel cased.information
entries of speech rule tests are of a similar form:"${Locale} ${Domain} ${Description}"
- Names and information entries of other tests follow a similar pattern, starting with the description of the common aspect moving to the more specialised one. E.g.,
There are multiple ways to run tests for SRE.
- From within the
speech-rule-engine
repository withsre-tests
asgit submodule
. - From within the
speech-rule-engine
repository with a local copy of thesre-tests
repository. - From within the
sre-tests
repository with respect to a local copy of thespeech-rule-engine
repository.
The last case is the most common one used during development and is therefore described first.
First we need to tell the tests repo where SRE is located locally. This is by
setting the SRE_JSON_PATH
variable, e.g., as
export SRE_JSON_PATH=../speech-rule-engine/lib/mathmaps
Note, that setting this path can lead to unpredictable behaviour if running SRE in that shell.
pnpm prepare
will build all the necessary files for the tests as well as for analytics. Those files will be
pnpm test
runs tests separately without producing output. So there are no interactions between single test suites.
pnpm test-output
runs output producing tests only. Combines tests that share common output files into a single suite.
pnpm test-all
runs first singleton tests and then output producing tests.
pnpm clean
Removes all files generated by transpiling and testing.
npx jest --testPathPattern js/json/de/
will run German locale only.
npx jest --testPathPattern js/json/de/clearspeak js/json/base
will run Clearspeak German tests and base tests. Generally the switch --testPathPattern
can be omitted. E.g.,
npx jest js/output
will run all the test that produce output files.
Run with
make test (ENV=n)
ENV
are optional environment parameters.
├── expected JSON files with expected output of tests
├── input JSON files with input of tests
├── output Output files generated by tests (mainly HTML files)
└── ts Source files for tests
Source directory effectively consists of two parts:
- Static sources that are used in test classes or for auxiliary task such as analytics or test generation.
- Dynamic sources that are generated by the
tests.config.js
script and that actually run tests.
└── ts
├── analytics Classes and utilities that run analytics on SRE.
├── base Common files used by test classes.
├── classes Classes for tests that take input and expected from JSON files.
├── generate Utility methods to generate JSON test files from various sources.
├── tests Test Classes that run tests via test functions.
└── typings Contains type definitions, e.g. for SRE.
These sources are generated by running pnpm prepare
or the build
function
from tests.config.js
. They are removed with the clean
function. The
generated files are of type .test.ts
so they can be run with jest
directly.
└── ts
├── actions Runs all tests with is one file per main test category. That is,
│ primarly one for each locale plus `base, semantic, walker`.
│ The main purpose of these tests is to be run as github actions.
├── analyse Files for running SRE analytics. There is on test file for every
│ `.json` file that contains a `name` entry in the `expected` directory.
│ Subdirectory structure is a mirror of the one in `expected`.
├── json Runs all tests. There is one test file per `.json` file in the `expected` directory.
│ Subdirectory structure is a mirror of the one in `expected`.
└── output Files for running tests that produce output. There is one file for each unique
entry given in the `active` field for test input files. That is, often multiple
test files are combined and run to generate on output file.
The directory structure is flat.
└── input
├── base Basic tests for components of the engine.
├── clearspeak Speech tests for clearspeak rules.
├── common Speech tests for common use (e.g., fonts, units)
├── mathspeak Speech tests for mathspeak rules.
└── nemeth Speech tests for (Nemeth) Braille.
└── expected
├── base Expected values for basic tests.
├── locale_1 Expected values for speech tests of locale_1.
│ ....
└── locale_n Expected values for speech tests of locale_n.
└── expected
└── locale
├── clearspeak Expected values for clearspeak tests
├── mathspeak Expected values for mathspeak tests.
├── others Expected values for all other speech rule tests (e.g., prefix, summary)
└── symbols Expected values for symbol tests (character, function, unit)
│ ...
└── nemeth
├── rules Expected values for nemeth rules
└── symbols Expected values for symbol tests (character, function, unit)
└── expected
│ Potentially other files (with various extensions).
│ ....
├── locale_1 HTML output for speech tests of locale_1.
│ ....
└── locale_n HTML output for speech tests of locale_n.
├── expected
│  ├── base
│  ├── de
│  │  ├── clearspeak
│  │  ├── mathspeak
│  │  ├── others
│  │  └── symbols
│  ├── en
│  │  ├── clearspeak
│  │  ├── mathspeak
│  │  ├── others
│  │  └── symbols
│  ├── es
│  │  ├── mathspeak
│  │  ├── others
│  │  └── symbols
│  ├── fr
│  │  ├── clearspeak
│  │  ├── mathspeak
│  │  ├── others
│  │  └── symbols
│  └── nemeth
│  ├── rules
│  └── symbols
├── input
│  ├── base
│  ├── clearspeak
│  ├── common
│  ├── mathspeak
│  └── nemeth
├── output
│  ├── de
│  ├── en
│  │  ├── sheets
│  │  └── symbols
│  ├── es
│  │  └── symbols
│  ├── fr
│  │  ├── functions
│  │  ├── sheets
│  │  ├── symbols
│  │  └── units
│  └── nemeth
│  ├── functions
│  ├── sheets
│  ├── symbols
│  └── units
└── ts
├── base
├── classes
└── tests
Run with
pnpm prepare
pnpm test:analytics
This will generate SRE analytics in the analysis
folder. In particular it will
generate the following subfolders, which contains json files of the following
nature:
├── allRules List of all rules available in each rule set.
├── applicableRules List of all applicable rules for each test run.
├── appliedRules List of all actually applied rules for each test run.
├── diffAppliedRules Difference list of rules applied and available for each rule set.
│ Full test coverage for a rule set means that this list is empty.
└── uniqueAppliedRules List of all unique rules applied during tests for test suite.
A number of useful modules are provided in the generate
directory, that ease
many tedious tasks surrounding the maintenance of the test suites, such test
generation, copying files, adding fields, correcting and updating tests, etc.
The majority of test generation, copying, etc. will be handled on the Node REPL. There are two ways to ensure that all the necessary files in SRE can be found.
-
Run node in a shell on where the
SRE_JSON_PATH
variable is set. Or start an Emacs that will run the REPL in such a shell. -
Set the
SRE_JSON_PATH
environment variable explicitly in node before loading a module.. For example:process.env['SRE_JSON_PATH'] = '../speech-rule-engine/lib/mathmaps'; (await import('./js/module_loader.js')).load('gt'); ...
Simplest way is to generate tests from experimental data using input transformers. The standard use case is this: You have a set of expressions from experiments, investigating an issue or conversion from sources or literature. These should now be transformed into tests. The basic approach then comprises three steps:
- Generate a test entries from the initial sources. We call these the proto tests. They can either be written either to a new or to an existing file.
- Transform the proto tests to generate all the required fields for the tests.
- Post-process tests for special cases.
- Compute expected output by running the necessary methods from the
fill_tests
module.
The following are methods to easily import tests that are either generated systematically or sporadically during experiments. Methods are loaded with
let gt = require('./js/generate/generate_tests.js');
There are currently four methods: fromJson
, fromList
, fromIssues
,
fromLines
. While they generally have different input parameters, all have an
output
parameter, which corresponds to the filename of the basic test file to
be created. This file will only contain the basic JSON structure with the
tests
field. All other parts have to be added manually.
However, if the output file already exists, or exists as file in the test input directory, tests will be appended to the existing content. Note that append can overwrite existing tests of the same name in the original test file!
We explain how the methods work, by splitting them by use cases.
Simply combine them into a single json file like below, where foo
and bar
represent names for the type of expression or input.
{
"foo": [d0, d1, d2, ....],
"bar": [e0, e1, e2, ....],
...
}
These case can be loaded and transformed into a regular JsonTests
structure
using method:
gt.fromJson(INPUT, OUTPUT, option FIELD);
This transforms the INPUT
file sets into OUTPUT
containing the following tests:
{
"foo_0": {"field": d0},
"foo_1": {"field": d1},
"foo_2": {"field": d2},
...
"bar_0": {"field": e0},
"bar_1": {"field": e1},
"bar_2": {"field": e2},
...
}
The optional FIELD
parameter can be used to specify the target field in the
single tests. E.g. for a set of tex expressions you might want to choose tex
as field so a transformer can be applied directly. FIELD
defaults to input
.
gt.fromJson(INPUT, OUTPUT, option NAME);
Generates enumerated tests from a list of test data entries, preserving comments. The input JSON is of the form
[
{"comment": "foo"},
{"field": d0},
{"field": d1},
{"field": d2},
{"comment": "bar"},
{"field": e0},
{"field": e1},
{"field": e2},
...
]
The method takes the name of the tests as optional parameters. It defaults to the basename of input file. The method will create output containing the following tests:
{
"_comment_0": "foo",
"test_0": {"field": d0},
"test_1": {"field": d1},
"test_2": {"field": d2},
"_comment_1": "bar",
"test_3": {"field": e0},
"test_4": {"field": e1},
"test_5": {"field": e2},
...
}
Often, when investigating an issue, one creates a number of separate input
files, each containing a single input expression. Commonly these files are
located in a common directory, e.g., separate .xml
files, but all with a
similar base name. These issue files can be reused and turned into a single test
file or added to an existing test file.
gt.fromIssues(dir, file, output);
Attaches tests from issue files in dir
with the basename file
to the
target
tests. For example, the following code will attach all expression for
issue_616
to the fences
tests:
gt.fromIssues(ISSUE_DIR, 'issue_616', 'semantic/fences.json');
The content of the file issue_616_0.xml
will be added as input with test name
issue_616_0
in the fences tests. Note, that currently expected values for
these tests still need to be added manually or with a fill_tests
method below.
Often it is easier to collate examples without setting them into some JSON format, e.g., by grepping out of a LaTeX style file manual, or copying from a rule book. We can then generate tests for single lines in an ASCII file. Thereby we assume that lines will be of the form:
name0
prop1
prop2
...
That is, the first line is taken as name of the test, the second line the value of property 1, third line value of property 2, and so on, according to the provided property list to the method:
gt.fromLines(INPUT, OUTPUT, PROPERTIES);
Thereby PROPERTIES
is an ordered list of properties to be parsed, which will
make up the test structure. For example,
gt.fromLines('./input.txt', './output.json', ['tex', 'brf']);
where input.txt
contains the following lines
Example 1.4-1
5\quad 10\quad 15\quad 20
#5 #10 #15 #20
will yield a test structure of the form:
{
...
"Example 1.4-1": {
"tex": "5\\quad 10\\quad 15\\quad 20",
"brf": "#5 #10 #15 #20"
},
...
}
Note, that empty lines will be considered as test separators. Thus a single test will be parsed until an empty line is encountered or until the property list is exhausted.
To further transform initial test content, apply one or more transformers to the proto test files. For example:
gt.transformJsonTests(INPUT, gt.getTransformers(['tex']));
Applies a TeX transformer to all tex
fields in INPUT
resulting in an input
field with a mml expression, writing the output to the same file. Other useful
transformers are found in braille_transformer.ts
.
For convenience there exist three transformer methods, each with three parameters:
gt method |
First input parameter | Comment |
---|---|---|
transformTest |
A single test | Transforms a single test |
transformTests |
A list of tests | Transforms a list of tests |
transformJsonTests |
A filename | Transforms a regular test file |
transformTestsFile |
A filename | Transforms a file containing a list of tests |
transformNamedTests |
A filename | Transforms named tests in a test file |
All methods methods take a list of transformers as second argument.
transformNamedTests
in addition takes a list of test names as third argument.
By default none of the methods will overwrite an existing entry in a test
structure. Therefore each transformer method has an optional last force
argument, that enforces overwrite when set to true.
Tests can be generated or regenerated using the fill_tests
module:
(await import('./js/module_loader.js')).load('ft');
Each of the following commands takes an path to a file with expected values and optionally a flag indicating if it is a dry run or if the changes are to be made destructively to the input file.
ft method |
Action |
---|---|
addMissing |
Adds expected values for all missing tests. |
addActual |
Overwrites all expected values with the actual ones. |
addFailed |
Overwrites expected values for all failed tests. |
removeMissing |
Removes tests in expected file that has been removed |
from the base file. |
showMissing
prints all missing tests for all available test files. Output can
be restricted by providing a regular expression for filtering filenames.
showFailed
prints all failed tests for all available test files. Output can be
restricted by providing a regular expression for filtering filenames. A second
parameter offers a dry run flag, which is true
by default. If set to false
it will correct all the incorrect tests.
Although test files that provide complete tests (i.e., containing both input
and expected
elements plus all necessary parameters) can be run, it is often
useful to have separate files for input
and expected
values in the
respective directories. For example, when running speech rule tests all locales
can use a single base file. Likewise the different types of semantic
interpretation tests can share the same base file.
Splitting a comprehensive file into a base file and one with expected
entries
only is done by:
gt.splitExpected(EXPECTED, BASE);
The EXPECTED
filename is considered relative to the expected
directory. The
name of the new base file is absolute. Note that this file will be overwritten!
So apply only once!
Basic tests are generated for each domain (default, mathspeak, clearspeak
). One file per symbol type: character, function, unit, si_unit
. These are reflected in the naming of the tests. All these tests are
for the default
style/preference only.
Extra tests are generated for all symbols that have an entry for non-default
styles in the extras_...json
files. Note, that these files do not have a
corresponding entry in the base
files. Note also, that each test entry comes
with its individual domain
and style
entry. Since one symbol can have
multiple entries, the name
elements is made unique by concatenating the
domain, style, symbol
and a special key
entry is actually used to represent
the symbol. Finally, currently no extra tests are generated for si_unit
.
(await import('./js/module_loader.js')).load('cto');
Make sure the AllConstraints
variable is up to date for all locales, before loading and running:
cto.allTests(?dir)
generates tests in dir
. If not given in /tmp/symbols
.
cto.replaceTests(?dir)
replaces expected tests output in the corresponding files of the
sre-tests/expected
directory. Note, that dir
corresponds to the directory of
test files generated by allTests
.
For all characters that are part of the automatically generated alphabets, as set of tests is used that works directly on the assumed correct markup (as numbers or identifiers). This primarily helps to find issues with fonts that might come from a discrepancy between the alphabet font and a semantically derived font.
These test files can be generated (or re-generated) with the following methods
cto.alphabetsBase()
generates the basic input file alphabets.json
in the sre-tests/input/common
directory.
cto.alphabetsExpected(locale)
cto.alphabetsAllExpected()
generates the basic expected file in the sre-tests/exected/$LOCALE
directory
with the appropriate domain prefix (default
, mathspeak
, ...) or expected
files for all locales. This will overwrite existing files that contain no
expected results entries!
Note, after generation, the expected files still need to be filled with the
appropriate addMissing
method call.
The following exported methods can be useful for specific updating of symbol tests of a locale:
These methods compute and return a JSON test structure. They take a locale
string and a symbol type
string as input.
cto method |
Usage |
---|---|
testFromBase |
Compute the tests from the base file for this symbol type. |
testFromLocale |
Compute the tests from the symbol files in the locale's mathmaps. |
testFromExtras |
Compute tests for all extra definitions (i.e., none default |
style) for the symbol type in the locale. |
These methods compute and save a JSON test structure. They take a locale
string and a symbol type
string as input. Optionally they take a target
directory for the output, which defaults to /tmp
.
cto method |
Usage |
---|---|
testOutputFromBase |
As testFromBase . |
testOutputFromLocale |
As testFromLocale . |
testOutputFromBoth |
Calls both previous methods in turn. |
testOutputFromExtras |
As testFromExtras . |
cto.testOutputFromBoth
and cto.testOutputFromExtras
are called in cto.allTests
.
splitNemethForFire diffBaseVsLocale
(await import('./js/module_loader.js')).load('ct');
Methods to copy test files from existing tests for a locale to tests for a new locale. Replaces all expected test entries with the actual results of tests. Hence can also overwrite already existing tests.
Simple message replacement for information, output and analysis files is carried out. This assumes that these files are canonically named as described above.
ct.copyTestLocale('en/mathspeak/mathspeak_tests.json', 'it', 'English', 'Italian');
Copies Mathspeak tests in English to the Italian locale. Messages/filenames
containing English
as substring are replaced with equivalents containing
Italian
.
ct.copyTestLocaleDir('en/', 'it', 'English', 'Italian');
Recursively performs the above action on a subdirectory.
Show the difference between test files existing in locale directories.
ct.localeDifference('en', 'it');
They normally reside in the semantic
test directory. The actual tests consist
of six different types, therefore their expected files are given in six
different sub-directories:
enrich_mathml
, translate to enriched Mathml output.enrich_speech
, test if speech strings computed directly for a MathML expression are equivalent to those computed for enriched expressions.rebuild_stree
, test if semantic trees build from enriched MathML expressions are equivalent to those computed directly.semantic_api
, tests consistency of the various semantic APIs.semantic_tree
, translate into semantic trees.semantic_xml
, tests consistency of the semantic tree parser, i.e., parsing the XML output of the semantic tree results in the equivalent semantic tree.deep_speech
, tests in an enriched Mathml element with deep speech, that every tree element with a semantic id has speech attached and vice versa.
Note that tests 2, 3, 4, 6 and 7, usually run with respect to "tests": "ALL"
.
Given a base file BASE
in the input
directory, we can generate the basic
structure for the above tests using
ct.copySemanticTest(BASE, fill?, targetdir?);
Note, that neither the semantic_tree
nor the enrich_mathml
tests will be
filled with expected. This has to be either done manually, e.g., using
ft.addMissing
or by setting the fill
flag to true
.
If only BASE
is provided, files will copied into a similar directory structure
from the input
directory into the expected
directory. This can be altered by
providing an explicit targetdir
parameters. For example,
ct.copySemanticTest('common/direct_speech.json', true, 'semantic');
will generate direct_speech.json
files in the semantic
directory with base
file residing in common
.
Test visualisation aim mainly at checking semantic interpretations. They can also be used for
let vis = require('./js/generate/visualise.js');
vis.visualise();
vis.OPTIONS;
{ LOCAL: false, TEX: true, VISUALISE: true }