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

GitHub workflow to ensure tests are up to date #1505

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ module.exports = {
},
env: {
es2020: true,
'shared-node-browser': true,
},
parserOptions: {
ecmaVersion: 'latest',
Expand Down
33 changes: 33 additions & 0 deletions .github/workflows/validate-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Validate Tests

on:
pull_request:
push:
branches:
- main
- 'release-*'
workflow_dispatch:

jobs:
validate:
name: Validate
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: Setup JS
uses: ./.github/actions/setup-js

- name: yarn gentest-validate
run: yarn gentest-validate

- name: yarn gentest
run: yarn gentest -h

- name: Check for modified tests
run: |
if [[ -n $(git status -s) ]]; then
git status -s
echo "yarn gentest modified these tests. Please run yarn gentest to resolve."
exit 1
fi
9 changes: 4 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

Yoga is an embeddable and performant flexbox layout engine with bindings for multiple languages.


## Building

Yoga's main implementation targets C++ 20 with accompanying build logic in CMake. A wrapper is provided to build the main library and run unit tests.

```sh
Expand All @@ -14,8 +14,6 @@ While not required, this script will use [ninja](https://ninja-build.org/) if it

Yoga is additionally part of the [vcpkg](https://github.com/Microsoft/vcpkg/) collection of ports maintained by Microsoft and community contributors. If the version is out of date, please [create an issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg repository.



## Adding Tests

Many of Yoga's tests are automatically generated, using HTML fixtures describing node structure. These are rendered in Chrome to generate an expected layout result for the tree. New fixtures can be added to `gentest/fixtures`.
Expand All @@ -27,9 +25,10 @@ Many of Yoga's tests are automatically generated, using HTML fixtures describing
```

To generate new tests from added fixtures:
1. Run `bundle install` in the `gentest` directory to install dependencies of the test generator.
2. Run `ruby gentest.rb` in the `gentest` directory.

1. Ensure you have [yarn](https://yarnpkg.com/) installed.
2. Run `yarn install` to install dependencies for the test generator.
3. Run `yarn gentest` in the `yoga` directory.

## Debugging

Expand Down
4 changes: 0 additions & 4 deletions gentest/Gemfile

This file was deleted.

31 changes: 0 additions & 31 deletions gentest/Gemfile.lock

This file was deleted.

12 changes: 12 additions & 0 deletions gentest/babel.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/

module.exports = {
presets: ['@babel/preset-typescript'],
};
135 changes: 135 additions & 0 deletions gentest/gentest-driver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/

import * as fs from 'node:fs/promises';
import {format} from 'node:util';
import {parse, dirname} from 'path';
import * as process from 'node:process';
import {Builder, logging} from 'selenium-webdriver';
import {Options} from 'selenium-webdriver/chrome.js';
import {fileURLToPath} from 'url';
import {stdin, stdout} from 'node:process';
import minimist from 'minimist';
import readline from 'node:readline/promises';
import signedsource from 'signedsource';

function addSignatureToSourceCode(sourceCode: string): string {
const codeWithToken = sourceCode.replace(
'MAGIC_PLACEHOLDER',
signedsource.getSigningToken(),
);

return signedsource.signFile(codeWithToken);
}

const argv = minimist(process.argv.slice(2));
const specificFixture = argv.f || argv.fixture;
const suspend = argv.s || argv.suspend;
const headless = argv.h || argv.headless;

const gentestDir = dirname(fileURLToPath(import.meta.url));
const yogaDir = dirname(gentestDir);

let fixtures = await fs.readdir(`${gentestDir}/fixtures`);
try {
if (specificFixture != null) {
await fs.access(`fixtures/${specificFixture}.html`, fs.constants.F_OK);
fixtures = [specificFixture + '.html'];
}
} catch (e) {
const errorMessage = e instanceof Error ? e.message : '';
console.log(
`Trying to access ${specificFixture}.html threw an exception. Executing against all fixtures. ${errorMessage}`,
);
}

const options = new Options();
options.addArguments(
'--force-device-scale-factor=1',
'--window-position=0,0',
'--hide-scrollbars',
);
headless && options.addArguments('--headless');
options.setLoggingPrefs({
browser: 'ALL',
performance: 'ALL',
});
const driver = await new Builder()
.forBrowser('chrome')
.setChromeOptions(options)
.build();

for (const fileName of fixtures) {
const fixture = await fs.readFile(
`${gentestDir}/fixtures/${fileName}`,
'utf8',
);
const fileNameNoExtension = parse(fileName).name;
console.log('Generate', fileNameNoExtension);

// TODO: replace this with something more robust than just blindly replacing
// start/end in the entire fixture
const ltrFixture = fixture
.replaceAll('start', 'left')
.replaceAll('end', 'right')
.replaceAll('flex-left', 'flex-start')
.replaceAll('flex-right', 'flex-end');

const rtlFixture = fixture
.replaceAll('start', 'right')
.replaceAll('end', 'left')
.replaceAll('flex-right', 'flex-start')
.replaceAll('flex-left', 'flex-end');

const template = await fs.readFile(
`${gentestDir}/test-template.html`,
'utf8',
);
const f = await fs.open(`${gentestDir}/test.html`, 'w');
await f.write(
format(template, fileNameNoExtension, ltrFixture, rtlFixture, fixture),
);
await f.close();

await driver.get('file://' + process.cwd() + '/test.html');
const logs = await driver.manage().logs().get(logging.Type.BROWSER);

await fs.writeFile(
`${yogaDir}/tests/generated/${fileNameNoExtension}.cpp`,
addSignatureToSourceCode(JSON.parse(logs[0].message.replace(/^[^"]*/, ''))),
);

await fs.writeFile(
`${yogaDir}/java/tests/com/facebook/yoga/${fileNameNoExtension}.java`,
addSignatureToSourceCode(
JSON.parse(logs[1].message.replace(/^[^"]*/, '')).replace(
'YogaTest',
fileNameNoExtension,
),
),
);

await fs.writeFile(
`${yogaDir}/javascript/tests/generated/${fileNameNoExtension}.test.ts`,
addSignatureToSourceCode(
JSON.parse(logs[2].message.replace(/^[^"]*/, '')).replace(
'YogaTest',
fileNameNoExtension,
),
),
);

if (suspend) {
const rl = readline.createInterface({input: stdin, output: stdout});
await rl.question('');
rl.close();
}
}
await fs.unlink(`${gentestDir}/test.html`);
await driver.quit();
40 changes: 40 additions & 0 deletions gentest/gentest-validate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/

import * as fs from 'node:fs/promises';
import {dirname} from 'path';
import {fileURLToPath} from 'url';
import signedsource from 'signedsource';

const yogaDir = dirname(dirname(fileURLToPath(import.meta.url)));
const cppTestDir = `${yogaDir}/tests/generated`;
const jsTestDir = `${yogaDir}/javascript/tests/generated`;
const javaTestDir = `${yogaDir}/java/tests/com/facebook/yoga`;
const testDirs = [cppTestDir, jsTestDir, javaTestDir];

for (const testDir of testDirs) {
const tests = await fs.readdir(testDir);

for (const test of tests) {
const testData = await fs.readFile(`${testDir}/${test}`, 'utf8');
try {
const validSignature = signedsource.verifySignature(testData);
if (!validSignature) {
console.error(`Invalid signature for ${test}`);
process.exitCode = 1;
}
} catch (e) {
// Java test dir does not separate generated tests from non-generated ones
if (testDir != javaTestDir) {
console.error(`${test}: ${e}`);
process.exitCode = 1;
}
}
}
}
8 changes: 4 additions & 4 deletions gentest/gentest.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,12 @@ function printTest(e, ext, LTRContainer, RTLContainer, genericContainer) {
' *',
' * This source code is licensed under the MIT license found in the',
' * LICENSE file in the root directory of this source tree.',
' */',
ext === 'cpp' ? '\n// clang-format off' : '',
'// @' +
'generated by gentest/gentest.rb from gentest/fixtures/' +
ext === 'cpp' ? ' *\n * clang-format off' : ' *',
` * MAGIC_PLACEHOLDER`,
' * generated by gentest/gentest-driver.ts from gentest/fixtures/' +
document.title +
'.html',
' */',
'',
]);
e.emitPrologue();
Expand Down
81 changes: 0 additions & 81 deletions gentest/gentest.rb

This file was deleted.

Loading
Loading