Skip to content

Commit

Permalink
Introduce a GitHub Actions workflow for running the font tests
Browse files Browse the repository at this point in the history
This commit migrates the font tests away from the bots. Not only are the
font tests broken on the Windows bot since some time, they also run on
Python 2 (end of life since January 2020) and `ttx` 3.19.0 (released in
November 2017). The latter is installed via a submodule, which requires
more complicated logic for finding and running `ttx`.

We solve the issues by implementing a modern workflow that installs the
most recent stable Python and `ttx` (`fonttools` package) versions. This
simplifies the `ttx` driver code as well because it can now assume `ttx`
is available on the path (just like we do for e.g. `node` invocations).
GitHub Actions takes care of creating a virtual environment with
`fonttools` in it so that the `ttx`  entrypoint is available. Locally
the font tests can be run in a similar way by creating and sourcing a
virtual environment with `fonttools` in it before running the font
tests, and a README file is included with instructions for doing so.
  • Loading branch information
timvandermeij committed Nov 12, 2023
1 parent 69452bb commit 8157f39
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 54 deletions.
64 changes: 64 additions & 0 deletions .github/workflows/font_tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
name: Font tests
on:
push:
paths:
- 'gulpfile.mjs'
- 'src/**'
- 'test/test.mjs'
- 'test/font/**'
- '.github/workflows/font_tests.yml'
branches:
- master
pull_request:
paths:
- 'gulpfile.mjs'
- 'src/**'
- 'test/test.mjs'
- 'test/font/**'
- '.github/workflows/font_tests.yml'
branches:
- master
workflow_dispatch:
permissions:
contents: read

jobs:
test:
name: Test

strategy:
fail-fast: false
matrix:
node-version: [lts/*]
os: [windows-latest, ubuntu-latest]

runs-on: ${{ matrix.os }}

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}

- name: Install Gulp
run: npm install -g gulp-cli

- name: Install other dependencies
run: npm install

- name: Use Python 3.12
uses: actions/setup-python@v4
with:
python-version: '3.12'
cache: 'pip'

- name: Install Fonttools
run: pip install fonttools

- name: Run font tests
run: gulp fonttest --headless
3 changes: 0 additions & 3 deletions .gitmodules

This file was deleted.

2 changes: 0 additions & 2 deletions gulpfile.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1749,7 +1749,6 @@ gulp.task(
return streamqueue(
{ objectMode: true },
createTestSource("unit", { bot: true }),
createTestSource("font", { bot: true }),
createTestSource("browser", { bot: true }),
createTestSource("integration")
);
Expand All @@ -1774,7 +1773,6 @@ gulp.task(
return streamqueue(
{ objectMode: true },
createTestSource("unit", { bot: true }),
createTestSource("font", { bot: true }),
createTestSource("browser", { bot: true, xfaOnly: true }),
createTestSource("integration")
);
Expand Down
36 changes: 36 additions & 0 deletions test/font/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Font tests

The font tests check if PDF.js can read font data correctly. For validation
the `ttx` tool (from the Python `fonttools` library) is used that can convert
font data to an XML format that we can easily use for assertions in the tests.
In the font tests we let PDF.js read font data and pass the PDF.js-interpreted
font data through `ttx` to check its correctness. The font tests are successful
if PDF.js can successfully read the font data and `ttx` can successfully read
the PDF.js-interpreted font data back, proving that PDF.js does not apply any
transformations that break the font data.

## Running the font tests

The font tests are run on GitHub Actions using the workflow defined in
`.github/workflows/font_tests.yml`, but it is also possible to run the font
tests locally. The current stable versions of the following dependencies are
required to be installed on the system:

- Python 3
- `fonttools` (see https://pypi.org/project/fonttools and https://github.com/fonttools/fonttools)

The recommended way of installing `fonttools` is using `pip` in a virtual
environment because it avoids having to do a system-wide installation and
therefore improves isolation, but any other way of installing `fonttools`
that makes `ttx` available in the `PATH` environment variable also works.

Using the virtual environment approach the font tests can be run locally by
creating and sourcing a virtual environment with `fonttools` installed in
it before running the font tests:

```
python3 -m venv venv
source venv/bin/activate
pip install fonttools
gulp fonttest
```
72 changes: 25 additions & 47 deletions test/font/ttxdriver.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,65 +14,43 @@
* limitations under the License.
*/

import { fileURLToPath } from "url";
import fs from "fs";
import os from "os";
import path from "path";
import { spawn } from "child_process";

const __dirname = path.dirname(fileURLToPath(import.meta.url));
let ttxTaskId = Date.now();

const ttxResourcesHome = path.join(__dirname, "..", "ttx");

let nextTTXTaskId = Date.now();

function runTtx(ttxResourcesHomePath, fontPath, registerOnCancel, callback) {
fs.realpath(ttxResourcesHomePath, function (error, realTtxResourcesHomePath) {
const fontToolsHome = path.join(realTtxResourcesHomePath, "fonttools-code");
fs.realpath(fontPath, function (errorFontPath, realFontPath) {
const ttxPath = path.join("Lib", "fontTools", "ttx.py");
if (!fs.existsSync(path.join(fontToolsHome, ttxPath))) {
callback("TTX was not found, please checkout PDF.js submodules");
return;
}
const ttxEnv = {
PYTHONPATH: path.join(fontToolsHome, "Lib"),
PYTHONDONTWRITEBYTECODE: true,
};
const ttxStdioMode = "ignore";
const python = process.platform !== "win32" ? "python2" : "python";
const ttx = spawn(python, [ttxPath, realFontPath], {
cwd: fontToolsHome,
stdio: ttxStdioMode,
env: ttxEnv,
});
let ttxRunError;
registerOnCancel(function (reason) {
ttxRunError = reason;
callback(reason);
ttx.kill();
});
ttx.on("error", function (errorTtx) {
ttxRunError = errorTtx;
callback("Unable to execute ttx");
});
ttx.on("close", function (code) {
if (ttxRunError) {
return;
}
callback();
});
});
function runTtx(fontPath, registerOnCancel, callback) {
const ttx = spawn("ttx", [fontPath], { stdio: "ignore" });
let ttxRunError;
registerOnCancel(function (reason) {
ttxRunError = reason;
callback(reason);
ttx.kill();
});
ttx.on("error", function (errorTtx) {
ttxRunError = errorTtx;
callback(
"Unable to execute `ttx`; make sure the `fonttools` dependency is installed"
);
});
ttx.on("close", function (code) {
if (ttxRunError) {
return;
}
callback();
});
}

function translateFont(content, registerOnCancel, callback) {
const buffer = Buffer.from(content, "base64");
const taskId = (nextTTXTaskId++).toString();
const fontPath = path.join(ttxResourcesHome, taskId + ".otf");
const resultPath = path.join(ttxResourcesHome, taskId + ".ttx");
const taskId = (ttxTaskId++).toString();
const fontPath = path.join(os.tmpdir(), `pdfjs-font-test-${taskId}.otf`);
const resultPath = path.join(os.tmpdir(), `pdfjs-font-test-${taskId}.ttx`);

fs.writeFileSync(fontPath, buffer);
runTtx(ttxResourcesHome, fontPath, registerOnCancel, function (err) {
runTtx(fontPath, registerOnCancel, function (err) {
fs.unlinkSync(fontPath);
if (err) {
console.error(err);
Expand Down
1 change: 0 additions & 1 deletion test/ttx/README.md

This file was deleted.

1 change: 0 additions & 1 deletion test/ttx/fonttools-code
Submodule fonttools-code deleted from d81701

0 comments on commit 8157f39

Please sign in to comment.