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

chore(core): dx: ldml test improvement, backspace test 🙀 #9759

Merged
merged 8 commits into from
Oct 20, 2023
33 changes: 20 additions & 13 deletions common/web/types/src/ldml-keyboard/ldml-keyboard-testdata-xml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,34 +41,41 @@ export interface LKTTests {
export interface LKTTest {
name?: string;
startContext?: LKTStartContext;
actions?: LKTAction[]; // differs from XML, to represent order of actions
actions?: LKTAnyAction[]; // differs from XML, to represent order of actions
};

export interface LKTStartContext {
to?: string;
};

export interface LKTCheck {
/**
* Test Actions.
* The expectation is that each LKTAction object will have exactly one non-falsy field.
*/
export interface LKTAction {
type?: "check" | "emit" | "keystroke" | "backspace";
};

export interface LKTCheck extends LKTAction {
type: "check";
result?: string;
};

export interface LKTEmit {
export interface LKTEmit extends LKTAction {
type: "emit";
to?: string;
};

export interface LKTKeystroke {
export interface LKTKeystroke extends LKTAction {
type: "keystroke";
key?: string;
flick?: string;
longPress?: string;
tapCount?: string;
};

/**
* Test Actions.
* The expectation is that each LKTAction object will have exactly one non-falsy field.
*/
export interface LKTAction {
check?: LKTCheck;
emit?: LKTEmit;
keystroke?: LKTKeystroke;
};
export interface LKTBackspace extends LKTAction {
type: "backspace";
}

export type LKTAnyAction = LKTCheck | LKTEmit | LKTKeystroke | LKTBackspace;
13 changes: 5 additions & 8 deletions common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -393,15 +393,12 @@ export class LDMLKeyboardXMLSourceFileReader {
r.stuffBoxes(test, $$, 'startContext'); // singleton
// now the actions
test.actions = $$.map(v => {
const subtag = v['#name'];
const subv = LDMLKeyboardXMLSourceFileReader.defaultMapper(v, r);
switch(subtag) {
case 'keystroke': return { keystroke: subv };
case 'check': return { check: subv };
case 'emit': return { emit: subv };
case 'startContext': return null; // handled above
default: this.callbacks.reportMessage(CommonTypesMessages.Error_TestDataUnexpectedAction({ subtag })); return null;
const type = v['#name']; // element name
if (type === 'startContext') {
return null; // handled above
}
const subv = LDMLKeyboardXMLSourceFileReader.defaultMapper(v, r);
return Object.assign({ type }, subv);
mcdurdin marked this conversation as resolved.
Show resolved Hide resolved
}).filter(v => v !== null);
return test;
});
Expand Down
4 changes: 0 additions & 4 deletions common/web/types/src/util/common-events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,4 @@ export class CommonTypesMessages {
m(this.ERROR_TestDataUnexpectedArray,
`Problem reading test data: expected single ${o.subtag} element, found multiple`);
static ERROR_TestDataUnexpectedArray = SevError | 0x0007;
static Error_TestDataUnexpectedAction = (o: {subtag: string}) =>
m(this.ERROR_TestDataUnexpectedAction,
`Problem reading test data: unexpected action element ${o.subtag}`);
static ERROR_TestDataUnexpectedAction = SevError | 0x0008;
};
8 changes: 8 additions & 0 deletions common/web/types/test/fixtures/test-fr.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE keyboardTest3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboardTest3.dtd">
<keyboardTest3 conformsTo="techpreview">
<!--
Read by:
- common/web/types/test/ldml-keyboard/test-ldml-keyboard-testdata-reader.ts

Also see the other test-fr.xml in developer
-->
<info keyboard="fr-t-k0-azerty.xml" author="Team Keyboard" name="fr-test" />
<repertoire name="simple-repertoire" chars="[a b c d e \u{22}]" type="simple" /> <!-- verify that these outputs are all available from simple keys on any layer, for all form factors -->
<repertoire name="chars-repertoire" chars="[á é ó]" type="gesture" /> <!-- verify that these outputs are all available from simple or gesture keys on any layer, for touch -->
Expand All @@ -17,6 +23,8 @@
<!-- tests by specifying 'to' output char -->
<emit to="v"/>
<check result="abc\u0022...stuv" />
<backspace />
<check result="abc\u0022...stu" />
</test>
</tests>
</keyboardTest3>
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import { constants } from '@keymanapp/ldml-keyboard-constants';
import { assert } from 'chai';
import 'mocha';
import { testTestdataReaderCases } from '../helpers/reader-callback-test.js';
import { LKTAnyAction } from './ldml-keyboard-testdata-xml.js';

describe('ldml keyboard xml reader tests', function () {
this.slow(500); // 0.5 sec -- json schema validation takes a while

testTestdataReaderCases([
{
// Note! There's another test case against similar data, in developer/src/kmc-ldml/test/test-testdata-e2e.ts using test-fr.json
subpath: 'test-fr.xml',
callback: (data, source) => {
assert.ok(source);
Expand Down Expand Up @@ -35,16 +37,19 @@ describe('ldml keyboard xml reader tests', function () {
const test0 = source.keyboardTest3.tests[0].test[0];
assert.equal('key-test', test0.name);
assert.equal('abc\\u0022...', test0.startContext?.to);
assert.sameDeepOrderedMembers([
{ keystroke: { key: 's' } },
{ check: { result: 'abc\\u0022...s' } },
{ keystroke: { key: 't' } },
{ check: { result: 'abc\\u0022...st' } },
{ keystroke: { key: 'u' } },
{ check: { result: 'abc\\u0022...stu' } },
{ emit: { to: 'v' } },
{ check: { result: 'abc\\u0022...stuv' } },
], test0.actions);
const expectedActions : LKTAnyAction[] = [
{ type: "keystroke", key: 's' },
{ type: "check", result: 'abc\\u0022...s' },
{ type: "keystroke", key: 't' },
{ type: "check", result: 'abc\\u0022...st' },
{ type: "keystroke", key: 'u' },
{ type: "check", result: 'abc\\u0022...stu' },
{ type: "emit", to: 'v' },
{ type: "check", result: 'abc\\u0022...stuv' },
{ type: "backspace" },
{ type: "check", result: 'abc\\u0022...stu' },
];
assert.sameDeepOrderedMembers(expectedActions, test0.actions, 'Static data in .ts file should match parsed test-fr.xml');
},
}
]);
Expand Down
15 changes: 15 additions & 0 deletions core/tests/unit/ldml/keyboards/k_006_backspace-test.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE keyboardTest3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboardTest3.dtd">
<keyboardTest3 conformsTo="techpreview">
<info keyboard="k_006_backspace.xml" author="Team Keyboard" name="backspace" />
<tests name="backspace">
<test name="regex-test-0-a">
<startContext to="" />
<keystroke key="t" />
<keystroke key="a" />
<keystroke key="b" />
<backspace />
<check result="ta" /> <!-- TODO-LDML: #9760 Should be 't' due to backspace transform -->
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 test verifies that <backspace /> works at all, #9760 to fix it for transforms

</test>
</tests>
</keyboardTest3>
7 changes: 0 additions & 7 deletions core/tests/unit/ldml/keyboards/k_006_backspace.xml
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>

<!--
from https://github.com/unicode-org/cldr/blob/keyboard-preview/docs/ldml/tr35-keyboards.md#element-transform

@@keys: [K_T][K_A][K_B][K_BKSP]
@@expected: t

-->
<!DOCTYPE keyboard3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard3.dtd">
<keyboard3 locale="en" conformsTo="techpreview">
<info author="srl295" indicator="🙀" layout="qwerty" normalization="NFC" />
Expand Down
2 changes: 1 addition & 1 deletion core/tests/unit/ldml/keyboards/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ tests_without_testdata = [
'k_003_transform',
'k_004_tinyshift',
'k_005_modbittest',
# 'k_006_backspace', ## not quite there yet. TODO-LDML
'k_010_mt',
'k_011_mt_iso',
'k_100_keytest',
Expand All @@ -33,6 +32,7 @@ tests_without_testdata = [
# These tests have a k_001_tiny-test.xml file as well.
tests_with_testdata = [
'k_001_tiny',
'k_006_backspace',
'k_007_transform_rgx',
'k_008_transform_norm',
'k_020_fr', # TODO-LDML: move to cldr above (fix vkey)
Expand Down
64 changes: 47 additions & 17 deletions core/tests/unit/ldml/ldml.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ run_test(const km::kbp::path &source, const km::kbp::path &compiled, km::tests::

// Run through actions, applying output for each event
for (test_source.next_action(action); action.type != km::tests::LDML_ACTION_DONE; test_source.next_action(action)) {
// handle backspace here
if (action.type == km::tests::LDML_ACTION_KEY_EVENT) {
auto &p = action.k;
std::cout << "- key action: 0x" << std::hex << p.vk << "/modifier 0x" << p.modifier_state << std::dec << std::endl;
Expand Down Expand Up @@ -294,17 +295,25 @@ run_test(const km::kbp::path &source, const km::kbp::path &compiled, km::tests::
/**
* Run all tests for this keyboard
*/
int run_all_tests(const km::kbp::path &source, const km::kbp::path &compiled) {
int run_all_tests(const km::kbp::path &source, const km::kbp::path &compiled, const std::string &filter) {
std::wcout << console_color::fg(console_color::BLUE) << "source file = " << source << std::endl
<< "compiled file = " << compiled << console_color::reset() << std::endl;
if(!filter.empty()) {
std::wcout << "Running only tests matching (substring search): " << filter.c_str() << std::endl;
}

km::tests::LdmlEmbeddedTestSource embedded_test_source;

std::vector<std::string> failures; // track failures for summary

int embedded_result = embedded_test_source.load_source(source);

if (embedded_result == 0) {
if (!filter.empty()) {
// Always skip the embedded test if there's a filter.
std::wcout << console_color::fg(console_color::YELLOW) << "SKIP: " << source.name() << " (embedded)" << console_color::reset()
<< std::endl;
embedded_result = 0; // no error
} else if (embedded_result == 0) {
// embedded loaded OK, try it
std::wcout << console_color::fg(console_color::BLUE) << console_color::bold() << "TEST: " << source.name() << " (embedded)"
<< console_color::reset() << std::endl;
Expand All @@ -325,31 +334,43 @@ int run_all_tests(const km::kbp::path &source, const km::kbp::path &compiled) {
if (json_result != -1) {
const km::tests::JsonTestMap& json_tests = json_factory.get_tests();

size_t skip_count = 0;
assert(json_tests.size() > 0);
// Loop over all tests
for (const auto& n : json_tests) {
std::wcout << console_color::fg(console_color::BLUE) << console_color::bold() << "TEST: " << json_path.stem().c_str() << "/" << n.first.c_str() << console_color::reset() << std::endl;
const auto test_name = n.first;
auto qq = test_name.find(filter);
if (filter == "--list" || (qq == std::string::npos)) {
skip_count ++;
std::wcout << console_color::fg(console_color::YELLOW) << "SKIP: " << json_path.stem().c_str() << "/" << console_color::bold() << n.first.c_str() << console_color::reset() << std::endl;
continue;
}
std::wcout << console_color::fg(console_color::BLUE) << "TEST: " << json_path.stem().c_str() << "/" << console_color::bold() << n.first.c_str() << console_color::reset() << std::endl;
int sub_test = run_test(source, compiled, *n.second);
if (sub_test != 0) {
std::wcout << console_color::fg(console_color::BRIGHT_RED) << "FAIL: " << json_path.stem() << "/" << n.first.c_str()
std::wcout << console_color::fg(console_color::BRIGHT_RED) << "FAIL: " << json_path.stem() << "/" << console_color::bold() << n.first.c_str()
<< console_color::reset() << std::endl;
failures.push_back(json_path.stem() + "/" + n.first);
json_result = sub_test; // set to last failure
} else {
std::wcout << console_color::fg(console_color::GREEN) << " PASS: " << console_color::reset() << json_path.stem()
<< "/" << n.first.c_str() << std::endl;
std::wcout << console_color::fg(console_color::GREEN) << "PASS: " << console_color::reset() << json_path.stem()
<< "/" << console_color::bold() << n.first.c_str() << std::endl;
}
}
auto all_count = json_tests.size();
auto all_count = json_tests.size();
auto fail_count = failures.size();
auto pass_count = all_count - fail_count;
auto pass_count = all_count - fail_count - skip_count;
if (pass_count > 0) {
std::wcout << console_color::fg(console_color::GREEN) << " +" << pass_count;
}
if (fail_count > 0) {
std::wcout << console_color::fg(console_color::BRIGHT_RED) <<
" -" << fail_count;
}
if (skip_count > 0) {
std::wcout << console_color::fg(console_color::YELLOW) <<
" (skipped " << skip_count << ")";
}
std::wcout << console_color::reset() << " of " << all_count << " JSON tests in "
<< json_path.stem() << std::endl;
}
Expand Down Expand Up @@ -386,10 +407,11 @@ int run_all_tests(const km::kbp::path &source, const km::kbp::path &compiled) {

constexpr const auto help_str =
"\
ldml [--color] <LDML_TEST_FILE> <KMX_FILE>\n\
ldml [--color] <LDML_FILE> <KMX_FILE> [ <TEST_FILTER> | --list ]\n\
help:\n\
\tKMN_FILE:\tThe ldml test file for the keyboard under test.\n\
\tKMX_FILE:\tThe corresponding compiled kmx file.\n";
\tLDML_FILE:\tThe .xml file for the keyboard under test.\n\
\tKMX_FILE:\tThe corresponding compiled kmx file.\n\
\tTEST_FILTER:\tIf present, only run json tests containing the filter substring. --list will list all tests\n";

} // namespace

Expand All @@ -402,20 +424,28 @@ int error_args() {
int main(int argc, char *argv[]) {
int first_arg = 1;

if (argc < 3) {
if ((argc - first_arg) < 2) { // if < 2 remaining args
return error_args();
}

auto arg_color = std::string(argv[1]) == "--color";
auto arg_color = std::string(argv[first_arg]) == "--color";
if(arg_color) {
first_arg++;
if(argc < 4) {
return error_args();
}
}
console_color::enabled = console_color::isaterminal() || arg_color;

int rc = run_all_tests(argv[first_arg], argv[first_arg + 1]);
if ((argc - first_arg) < 2) {
return error_args();
}
const km::kbp::path ldml_file = argv[first_arg++];
const km::kbp::path kmx_file = argv[first_arg++];

std::string filter; // default to 'all tests'
if ((argc - first_arg) >= 1) {
filter = argv[first_arg++];
}

int rc = run_all_tests(ldml_file, kmx_file, filter);
if (rc != EXIT_SUCCESS) {
std::wcerr << console_color::fg(console_color::BRIGHT_RED) << "FAILED" << console_color::reset() << std::endl;
rc = EXIT_FAILURE;
Expand Down
Loading