diff --git a/README.md b/README.md index e41deea..e7a3a13 100644 --- a/README.md +++ b/README.md @@ -100,8 +100,29 @@ This is a shortcut for `npm test -- --testFileName=MyTestableUnit` | Flag | Description | |------|-------------| | `--testFileName=` | Run only tests whose file name matches `` | +| `--tests=` | Run only tests matching semicolon-separated glob patterns, filtered at build time (see below) | | `--forceConnect` | Before deploying, kill any local process connected to port 8085 and proceed (see below) | +### Filtering tests with `--tests` + +`--tests` accepts semicolon-separated glob patterns (supports `*` and `?` wildcards) matched case-insensitively against unit names (i.e. the filename without `.test.brs`). Only matching test files are compiled and deployed — making it significantly faster than running the full suite. + +```shell +# Run all tests whose name starts with "Home" +npm test -- --tests="Home*" + +# Run tests for multiple groups in a single build+deploy +npm test -- --tests="Home*;Video*;Button" + +# Exact names (no wildcards needed) +npm test -- --tests="SomeService;Button" + +# Via environment variable +TESTS="Home*;Video*" npm test +``` + +Use `--testFileName` instead when you want to filter at the Roku device runtime level (all files still get deployed). Use `--tests` when you want to reduce build and deploy time by excluding unrelated tests entirely. + ### Port 8085 and debug session behaviour Roku exposes a debug console on **port 8085**. Only one client can be connected at a time. diff --git a/packager-steps/args.js b/packager-steps/args.js index b115883..0f14292 100644 --- a/packager-steps/args.js +++ b/packager-steps/args.js @@ -18,4 +18,18 @@ if (args.env === 'test' && ((scriptName === 'test' && args._.length) || args._.l args.testFileName = args._.slice(-1)[0]; } +/** + * @type {string} Semicolon-separated list of glob patterns to filter which test files are built and run. + * Supports * and ? wildcards. Matched against the unit name (filename without .test.brs). + * Takes precedence over --testFileName when provided. + * + * npm test -- --tests=Rail*;Video*;Tile* + * npm test -- --tests=RailsService;Tile + * TESTS=Rail*;Video* npm test + */ + +if (!args.tests && process.env.TESTS) { + args.tests = process.env.TESTS; +} + module.exports = args; diff --git a/plugins/generate-tests/prepare-test-schema/index.js b/plugins/generate-tests/prepare-test-schema/index.js index 5c2c6c0..f8b8e22 100644 --- a/plugins/generate-tests/prepare-test-schema/index.js +++ b/plugins/generate-tests/prepare-test-schema/index.js @@ -2,6 +2,7 @@ const fs = require('fs').promises; const { glob } = require('glob'); const { randomUUID } = require('crypto'); +const args = require('../../../packager-steps/args'); const TestSceneGenerator = require('./helpers/test-scene-generator'); const TestMainFileContentGenerator = require('./helpers/test-main-file-content-generator'); const TestSchema = require('./helpers/test-schema'); @@ -9,13 +10,25 @@ const TestXmlGenerator = require('./helpers/test-xml-generator'); const IGNORED_TEST_FILES_PATH_PATTERN = '/components/**/_tests/**/*_*.brs'; const MAIN_FILE_LOCATION = '/source/Main.brs'; +const TEST_FILE_NAME_REGEX = /[^/]+(?=\.test\.brs)/; const TEST_FILES_PATH_PATTERN = '/components/**/_tests/**/*.test.brs'; const TESTS_LOCATION = '/components/tests/auto-generated/'; module.exports = async function prepareTestSchema(dir) { - const testFilePaths = await glob(`${dir}${TEST_FILES_PATH_PATTERN}`, { + let testFilePaths = await glob(`${dir}${TEST_FILES_PATH_PATTERN}`, { ignore: `${dir}${IGNORED_TEST_FILES_PATH_PATTERN}`, }); + + if (args.tests) { + const patterns = args.tests.split(';').map((pattern) => pattern.trim()).filter(Boolean); + testFilePaths = testFilePaths.filter((filePath) => { + const unitName = filePath.match(TEST_FILE_NAME_REGEX)?.[0]; + + return unitName && patterns.some((pattern) => _matchesPattern(unitName, pattern)); + }); + console.log(`[generate-tests] --tests filter: ${patterns.join(', ')} → ${testFilePaths.length} test file(s)`); + } + const testXmlGenerator = new TestXmlGenerator(dir); await TestSceneGenerator.generate(`${dir}${TESTS_LOCATION}`); @@ -25,6 +38,15 @@ module.exports = async function prepareTestSchema(dir) { ); } +function _matchesPattern(str, pattern) { + const regex = new RegExp( + '^' + pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*').replace(/\?/g, '.') + '$', + 'i', + ); + + return regex.test(str); +} + async function prepareSchema(testFilePath, testXmlGenerator, rootDir) { const testSchema = await TestSchema.load(testFilePath); if (!testSchema) {