Latest commit

History

13 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

emx-testspec

A testspec engine for CLI E2E testing, inspired by Go's cmd/internal/script and roguepeppe/go-internal/testscript.

Overview

emx-testspec provides a declarative scripting DSL for testing CLI tools. Tests are written as txtar archives where the comment section contains script commands and the file section provides test fixtures.

Features

  • Declarative syntax - Simple, readable test scripts
  • Txtar integration - Test fixtures in the same file
  • Rich command set - exec, stdout, stderr, cmp, grep, etc.
  • Platform conditions - Conditional execution per OS/arch
  • Virtual files - stdout and stderr as file-like objects
  • Background processes - Run commands asynchronously
  • Flexible matching - Regex patterns with multi-line support
  • MIT License - Free to use in any project

Installation

Add to your Cargo.toml:

[dependencies]
emx-testspec = "0.1"

Or use via Git:

[dependencies]
emx-testspec = { git = "https://github.com/coreseekdev/emx-testspec" }

Requires emx-txtar as a dependency.

Quick Start

Basic Test

Create tests/mytool.txtar:

# Test basic functionality
exec mytool input.txt
stdout 'success'
-- input.txt --
test data

Run tests:

use emx_testspec::run_and_assert;#[test]fntest_mytool(){run_and_assert("tests");}

Using as CLI

Install the CLI:

cargo install emx-testspec --git https://github.com/coreseekdev/emx-testspec

Run tests:

emx-testspec tests/ # Run all tests
emx-testspec tests/test.txtar # Run single test
emx-testspec tests/ -v # Verbose output
emx-testspec tests/ -f "basic"# Filter by name
emx-testspec tests/ --keep # Preserve work directories

Script Syntax

Commands

CommandDescriptionExample
execExecute a commandexec mytool arg1 arg2
stdoutMatch stdout with regexstdout 'expected'
stderrMatch stderr with regexstderr 'error message'
cmpCompare filescmp file1 file2
cmpenvCompare with env expansioncmpenv file1 file2
grepSearch in filegrep 'pattern' file
catPrint file contentscat file
cdChange directorycd subdir
cpCopy filescp src dst
mvMove/rename filesmv old new
rmRemove filesrm file
mkdirCreate directoriesmkdir dir
existsCheck file existenceexists file
envSet/get environmentenv KEY=value
echoPrint to stdout bufferecho text
sleepWait for durationsleep 1s
stopStop test (non-error)stop 'reason'
skipSkip testskip 'reason'
helpList commandshelp

Prefixes

  • ! - Command must fail (exit code ≠ 0)
  • ? - Command may succeed or fail
  • No prefix - Command must succeed (exit code = 0)

Example:

exec mytool --valid # Must succeed
! exec mytool --invalid # Must fail
? exec mytool --unpredictable # Either is OK

Conditions

Conditional execution based on platform:

[unix] exec unix-tool
[windows] exec windows-tool
[linux] exec linux-tool
[amd64] exec x86_64-tool
[!windows] exec not-windows-tool
[exec:python] exec python-script

Environment Variables

Built-in Variables

  • $WORK or $TMPDIR - Temporary working directory
  • $PWD - Current working directory
  • ${/} - OS path separator (/ or \)
  • ${:} - Path list separator (: or ;)

Usage

env KEY=value
echo $KEY

In comparison:

cmp stdout $WORK/expected.txt

Virtual Files

stdout and stderr can be used as virtual files:

exec mytool
cmp stdout expected.txt
cp stderr debug.log

Advanced Usage

Background Processes

exec server &
sleep 2s
exec client --request
wait

Multi-line Matching

exec mytool
stdout 'line1.*line2.*line3'

File Comparison with Diff

exec mytool input.txt
cmp stdout output.txt

If files differ, unified diff is shown.

Environment Expansion

env NAME=World
echo "Hello $NAME"

In cmpenv, environment variables are expanded before comparison.

API Usage

Running Tests

use emx_testspec::{TestRunner,RunConfig};let config = RunConfig{dir:"tests".into(),filter:None,workdir_root:None,preserve_work:false,verbose:false,extensions:vec![".txtar".into()],setup:None,};let runner = TestRunner::new(config);let result = runner.run_all()?;assert!(result.all_passed());

Custom Commands

use emx_testspec::{Engine,Cmd,CmdResult};use anyhow::Result;structMyCustomCmd;implCmdforMyCustomCmd{fnusage(&self) -> (String,String){("mycmd".into(),"[args]".into())}fnrun(&self,args:&[String],state:&mutState) -> Result<CmdResult>{// Custom logic hereOk(CmdResult::Success)}}// Register custom commandletmut engine = Engine::new();
engine.register_command("mycmd",Box::new(MyCustomCmd));

Environment Variables for Testing

VariableEffect
TESTSCRIPT_VERBOSE=1Enable verbose logging
TESTSCRIPT_WORK=1Preserve working directories

Format Specification

See spec/00-princ-11-tool-testspec.mx for the full grammar specification.

BNF Grammar

script-line = prefix? condition* command-name arg* inline-comment?
prefix = "!" | "?"
condition = "[" "!"? cond-name (":" suffix)? "]"
command = identifier
arg = quoted-arg | unquoted-arg
quoted-arg = "'" (char | "''")* "'"

Documentation

License

MIT License - see LICENSE file for details.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Related Projects

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Latest commit

History

13 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

emx-testspec

A testspec engine for CLI E2E testing, inspired by Go's cmd/internal/script and roguepeppe/go-internal/testscript.

Overview

emx-testspec provides a declarative scripting DSL for testing CLI tools. Tests are written as txtar archives where the comment section contains script commands and the file section provides test fixtures.

Features

  • Declarative syntax - Simple, readable test scripts
  • Txtar integration - Test fixtures in the same file
  • Rich command set - exec, stdout, stderr, cmp, grep, etc.
  • Platform conditions - Conditional execution per OS/arch
  • Virtual files - stdout and stderr as file-like objects
  • Background processes - Run commands asynchronously
  • Flexible matching - Regex patterns with multi-line support
  • MIT License - Free to use in any project

Installation

Add to your Cargo.toml:

[dependencies]
emx-testspec = "0.1"

Or use via Git:

[dependencies]
emx-testspec = { git = "https://github.com/coreseekdev/emx-testspec" }

Requires emx-txtar as a dependency.

Quick Start

Basic Test

Create tests/mytool.txtar:

# Test basic functionality
exec mytool input.txt
stdout 'success'
-- input.txt --
test data

Run tests:

use emx_testspec::run_and_assert;#[test]fntest_mytool(){run_and_assert("tests");}

Using as CLI

Install the CLI:

cargo install emx-testspec --git https://github.com/coreseekdev/emx-testspec

Run tests:

emx-testspec tests/ # Run all tests
emx-testspec tests/test.txtar # Run single test
emx-testspec tests/ -v # Verbose output
emx-testspec tests/ -f "basic"# Filter by name
emx-testspec tests/ --keep # Preserve work directories

Script Syntax

Commands

CommandDescriptionExample
execExecute a commandexec mytool arg1 arg2
stdoutMatch stdout with regexstdout 'expected'
stderrMatch stderr with regexstderr 'error message'
cmpCompare filescmp file1 file2
cmpenvCompare with env expansioncmpenv file1 file2
grepSearch in filegrep 'pattern' file
catPrint file contentscat file
cdChange directorycd subdir
cpCopy filescp src dst
mvMove/rename filesmv old new
rmRemove filesrm file
mkdirCreate directoriesmkdir dir
existsCheck file existenceexists file
envSet/get environmentenv KEY=value
echoPrint to stdout bufferecho text
sleepWait for durationsleep 1s
stopStop test (non-error)stop 'reason'
skipSkip testskip 'reason'
helpList commandshelp

Prefixes

  • ! - Command must fail (exit code ≠ 0)
  • ? - Command may succeed or fail
  • No prefix - Command must succeed (exit code = 0)

Example:

exec mytool --valid # Must succeed
! exec mytool --invalid # Must fail
? exec mytool --unpredictable # Either is OK

Conditions

Conditional execution based on platform:

[unix] exec unix-tool
[windows] exec windows-tool
[linux] exec linux-tool
[amd64] exec x86_64-tool
[!windows] exec not-windows-tool
[exec:python] exec python-script

Environment Variables

Built-in Variables

  • $WORK or $TMPDIR - Temporary working directory
  • $PWD - Current working directory
  • ${/} - OS path separator (/ or \)
  • ${:} - Path list separator (: or ;)

Usage

env KEY=value
echo $KEY

In comparison:

cmp stdout $WORK/expected.txt

Virtual Files

stdout and stderr can be used as virtual files:

exec mytool
cmp stdout expected.txt
cp stderr debug.log

Advanced Usage

Background Processes

exec server &
sleep 2s
exec client --request
wait

Multi-line Matching

exec mytool
stdout 'line1.*line2.*line3'

File Comparison with Diff

exec mytool input.txt
cmp stdout output.txt

If files differ, unified diff is shown.

Environment Expansion

env NAME=World
echo "Hello $NAME"

In cmpenv, environment variables are expanded before comparison.

API Usage

Running Tests

use emx_testspec::{TestRunner,RunConfig};let config = RunConfig{dir:"tests".into(),filter:None,workdir_root:None,preserve_work:false,verbose:false,extensions:vec![".txtar".into()],setup:None,};let runner = TestRunner::new(config);let result = runner.run_all()?;assert!(result.all_passed());

Custom Commands

use emx_testspec::{Engine,Cmd,CmdResult};use anyhow::Result;structMyCustomCmd;implCmdforMyCustomCmd{fnusage(&self) -> (String,String){("mycmd".into(),"[args]".into())}fnrun(&self,args:&[String],state:&mutState) -> Result<CmdResult>{// Custom logic hereOk(CmdResult::Success)}}// Register custom commandletmut engine = Engine::new();
engine.register_command("mycmd",Box::new(MyCustomCmd));

Environment Variables for Testing

VariableEffect
TESTSCRIPT_VERBOSE=1Enable verbose logging
TESTSCRIPT_WORK=1Preserve working directories

Format Specification

See spec/00-princ-11-tool-testspec.mx for the full grammar specification.

BNF Grammar

script-line = prefix? condition* command-name arg* inline-comment?
prefix = "!" | "?"
condition = "[" "!"? cond-name (":" suffix)? "]"
command = identifier
arg = quoted-arg | unquoted-arg
quoted-arg = "'" (char | "''")* "'"

Documentation

License

MIT License - see LICENSE file for details.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Related Projects

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

13 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

emx-testspec

A testspec engine for CLI E2E testing, inspired by Go's cmd/internal/script and roguepeppe/go-internal/testscript.

Overview

emx-testspec provides a declarative scripting DSL for testing CLI tools. Tests are written as txtar archives where the comment section contains script commands and the file section provides test fixtures.

Features

  • Declarative syntax - Simple, readable test scripts
  • Txtar integration - Test fixtures in the same file
  • Rich command set - exec, stdout, stderr, cmp, grep, etc.
  • Platform conditions - Conditional execution per OS/arch
  • Virtual files - stdout and stderr as file-like objects
  • Background processes - Run commands asynchronously
  • Flexible matching - Regex patterns with multi-line support
  • MIT License - Free to use in any project

Installation

Add to your Cargo.toml:

[dependencies]
emx-testspec = "0.1"

Or use via Git:

[dependencies]
emx-testspec = { git = "https://github.com/coreseekdev/emx-testspec" }

Requires emx-txtar as a dependency.

Quick Start

Basic Test

Create tests/mytool.txtar:

# Test basic functionality
exec mytool input.txt
stdout 'success'
-- input.txt --
test data

Run tests:

use emx_testspec::run_and_assert;#[test]fntest_mytool(){run_and_assert("tests");}

Using as CLI

Install the CLI:

cargo install emx-testspec --git https://github.com/coreseekdev/emx-testspec

Run tests:

emx-testspec tests/ # Run all tests
emx-testspec tests/test.txtar # Run single test
emx-testspec tests/ -v # Verbose output
emx-testspec tests/ -f "basic"# Filter by name
emx-testspec tests/ --keep # Preserve work directories

Script Syntax

Commands

CommandDescriptionExample
execExecute a commandexec mytool arg1 arg2
stdoutMatch stdout with regexstdout 'expected'
stderrMatch stderr with regexstderr 'error message'
cmpCompare filescmp file1 file2
cmpenvCompare with env expansioncmpenv file1 file2
grepSearch in filegrep 'pattern' file
catPrint file contentscat file
cdChange directorycd subdir
cpCopy filescp src dst
mvMove/rename filesmv old new
rmRemove filesrm file
mkdirCreate directoriesmkdir dir
existsCheck file existenceexists file
envSet/get environmentenv KEY=value
echoPrint to stdout bufferecho text
sleepWait for durationsleep 1s
stopStop test (non-error)stop 'reason'
skipSkip testskip 'reason'
helpList commandshelp

Prefixes

  • ! - Command must fail (exit code ≠ 0)
  • ? - Command may succeed or fail
  • No prefix - Command must succeed (exit code = 0)

Example:

exec mytool --valid # Must succeed
! exec mytool --invalid # Must fail
? exec mytool --unpredictable # Either is OK

Conditions

Conditional execution based on platform:

[unix] exec unix-tool
[windows] exec windows-tool
[linux] exec linux-tool
[amd64] exec x86_64-tool
[!windows] exec not-windows-tool
[exec:python] exec python-script

Environment Variables

Built-in Variables

  • $WORK or $TMPDIR - Temporary working directory
  • $PWD - Current working directory
  • ${/} - OS path separator (/ or \)
  • ${:} - Path list separator (: or ;)

Usage

env KEY=value
echo $KEY

In comparison:

cmp stdout $WORK/expected.txt

Virtual Files

stdout and stderr can be used as virtual files:

exec mytool
cmp stdout expected.txt
cp stderr debug.log

Advanced Usage

Background Processes

exec server &
sleep 2s
exec client --request
wait

Multi-line Matching

exec mytool
stdout 'line1.*line2.*line3'

File Comparison with Diff

exec mytool input.txt
cmp stdout output.txt

If files differ, unified diff is shown.

Environment Expansion

env NAME=World
echo "Hello $NAME"

In cmpenv, environment variables are expanded before comparison.

API Usage

Running Tests

use emx_testspec::{TestRunner,RunConfig};let config = RunConfig{dir:"tests".into(),filter:None,workdir_root:None,preserve_work:false,verbose:false,extensions:vec![".txtar".into()],setup:None,};let runner = TestRunner::new(config);let result = runner.run_all()?;assert!(result.all_passed());

Custom Commands

use emx_testspec::{Engine,Cmd,CmdResult};use anyhow::Result;structMyCustomCmd;implCmdforMyCustomCmd{fnusage(&self) -> (String,String){("mycmd".into(),"[args]".into())}fnrun(&self,args:&[String],state:&mutState) -> Result<CmdResult>{// Custom logic hereOk(CmdResult::Success)}}// Register custom commandletmut engine = Engine::new();
engine.register_command("mycmd",Box::new(MyCustomCmd));

Environment Variables for Testing

VariableEffect
TESTSCRIPT_VERBOSE=1Enable verbose logging
TESTSCRIPT_WORK=1Preserve working directories

Format Specification

See spec/00-princ-11-tool-testspec.mx for the full grammar specification.

BNF Grammar

script-line = prefix? condition* command-name arg* inline-comment?
prefix = "!" | "?"
condition = "[" "!"? cond-name (":" suffix)? "]"
command = identifier
arg = quoted-arg | unquoted-arg
quoted-arg = "'" (char | "''")* "'"

Documentation

License

MIT License - see LICENSE file for details.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Related Projects

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

13 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

emx-testspec

A testspec engine for CLI E2E testing, inspired by Go's cmd/internal/script and roguepeppe/go-internal/testscript.

Overview

emx-testspec provides a declarative scripting DSL for testing CLI tools. Tests are written as txtar archives where the comment section contains script commands and the file section provides test fixtures.

Features

  • Declarative syntax - Simple, readable test scripts
  • Txtar integration - Test fixtures in the same file
  • Rich command set - exec, stdout, stderr, cmp, grep, etc.
  • Platform conditions - Conditional execution per OS/arch
  • Virtual files - stdout and stderr as file-like objects
  • Background processes - Run commands asynchronously
  • Flexible matching - Regex patterns with multi-line support
  • MIT License - Free to use in any project

Installation

Add to your Cargo.toml:

[dependencies]
emx-testspec = "0.1"

Or use via Git:

[dependencies]
emx-testspec = { git = "https://github.com/coreseekdev/emx-testspec" }

Requires emx-txtar as a dependency.

Quick Start

Basic Test

Create tests/mytool.txtar:

# Test basic functionality
exec mytool input.txt
stdout 'success'
-- input.txt --
test data

Run tests:

use emx_testspec::run_and_assert;#[test]fntest_mytool(){run_and_assert("tests");}

Using as CLI

Install the CLI:

cargo install emx-testspec --git https://github.com/coreseekdev/emx-testspec

Run tests:

emx-testspec tests/ # Run all tests
emx-testspec tests/test.txtar # Run single test
emx-testspec tests/ -v # Verbose output
emx-testspec tests/ -f "basic"# Filter by name
emx-testspec tests/ --keep # Preserve work directories

Script Syntax

Commands

CommandDescriptionExample
execExecute a commandexec mytool arg1 arg2
stdoutMatch stdout with regexstdout 'expected'
stderrMatch stderr with regexstderr 'error message'
cmpCompare filescmp file1 file2
cmpenvCompare with env expansioncmpenv file1 file2
grepSearch in filegrep 'pattern' file
catPrint file contentscat file
cdChange directorycd subdir
cpCopy filescp src dst
mvMove/rename filesmv old new
rmRemove filesrm file
mkdirCreate directoriesmkdir dir
existsCheck file existenceexists file
envSet/get environmentenv KEY=value
echoPrint to stdout bufferecho text
sleepWait for durationsleep 1s
stopStop test (non-error)stop 'reason'
skipSkip testskip 'reason'
helpList commandshelp

Prefixes

  • ! - Command must fail (exit code ≠ 0)
  • ? - Command may succeed or fail
  • No prefix - Command must succeed (exit code = 0)

Example:

exec mytool --valid # Must succeed
! exec mytool --invalid # Must fail
? exec mytool --unpredictable # Either is OK

Conditions

Conditional execution based on platform:

[unix] exec unix-tool
[windows] exec windows-tool
[linux] exec linux-tool
[amd64] exec x86_64-tool
[!windows] exec not-windows-tool
[exec:python] exec python-script

Environment Variables

Built-in Variables

  • $WORK or $TMPDIR - Temporary working directory
  • $PWD - Current working directory
  • ${/} - OS path separator (/ or \)
  • ${:} - Path list separator (: or ;)

Usage

env KEY=value
echo $KEY

In comparison:

cmp stdout $WORK/expected.txt

Virtual Files

stdout and stderr can be used as virtual files:

exec mytool
cmp stdout expected.txt
cp stderr debug.log

Advanced Usage

Background Processes

exec server &
sleep 2s
exec client --request
wait

Multi-line Matching

exec mytool
stdout 'line1.*line2.*line3'

File Comparison with Diff

exec mytool input.txt
cmp stdout output.txt

If files differ, unified diff is shown.

Environment Expansion

env NAME=World
echo "Hello $NAME"

In cmpenv, environment variables are expanded before comparison.

API Usage

Running Tests

use emx_testspec::{TestRunner,RunConfig};let config = RunConfig{dir:"tests".into(),filter:None,workdir_root:None,preserve_work:false,verbose:false,extensions:vec![".txtar".into()],setup:None,};let runner = TestRunner::new(config);let result = runner.run_all()?;assert!(result.all_passed());

Custom Commands

use emx_testspec::{Engine,Cmd,CmdResult};use anyhow::Result;structMyCustomCmd;implCmdforMyCustomCmd{fnusage(&self) -> (String,String){("mycmd".into(),"[args]".into())}fnrun(&self,args:&[String],state:&mutState) -> Result<CmdResult>{// Custom logic hereOk(CmdResult::Success)}}// Register custom commandletmut engine = Engine::new();
engine.register_command("mycmd",Box::new(MyCustomCmd));

Environment Variables for Testing

VariableEffect
TESTSCRIPT_VERBOSE=1Enable verbose logging
TESTSCRIPT_WORK=1Preserve working directories

Format Specification

See spec/00-princ-11-tool-testspec.mx for the full grammar specification.

BNF Grammar

script-line = prefix? condition* command-name arg* inline-comment?
prefix = "!" | "?"
condition = "[" "!"? cond-name (":" suffix)? "]"
command = identifier
arg = quoted-arg | unquoted-arg
quoted-arg = "'" (char | "''")* "'"

Documentation

License

MIT License - see LICENSE file for details.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Related Projects

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Latest commit

History

13 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

emx-testspec

A testspec engine for CLI E2E testing, inspired by Go's cmd/internal/script and roguepeppe/go-internal/testscript.

Overview

emx-testspec provides a declarative scripting DSL for testing CLI tools. Tests are written as txtar archives where the comment section contains script commands and the file section provides test fixtures.

Features

  • Declarative syntax - Simple, readable test scripts
  • Txtar integration - Test fixtures in the same file
  • Rich command set - exec, stdout, stderr, cmp, grep, etc.
  • Platform conditions - Conditional execution per OS/arch
  • Virtual files - stdout and stderr as file-like objects
  • Background processes - Run commands asynchronously
  • Flexible matching - Regex patterns with multi-line support
  • MIT License - Free to use in any project

Installation

Add to your Cargo.toml:

[dependencies]
emx-testspec = "0.1"

Or use via Git:

[dependencies]
emx-testspec = { git = "https://github.com/coreseekdev/emx-testspec" }

Requires emx-txtar as a dependency.

Quick Start

Basic Test

Create tests/mytool.txtar:

# Test basic functionality
exec mytool input.txt
stdout 'success'
-- input.txt --
test data

Run tests:

use emx_testspec::run_and_assert;#[test]fntest_mytool(){run_and_assert("tests");}

Using as CLI

Install the CLI:

cargo install emx-testspec --git https://github.com/coreseekdev/emx-testspec

Run tests:

emx-testspec tests/ # Run all tests
emx-testspec tests/test.txtar # Run single test
emx-testspec tests/ -v # Verbose output
emx-testspec tests/ -f "basic"# Filter by name
emx-testspec tests/ --keep # Preserve work directories

Script Syntax

Commands

CommandDescriptionExample
execExecute a commandexec mytool arg1 arg2
stdoutMatch stdout with regexstdout 'expected'
stderrMatch stderr with regexstderr 'error message'
cmpCompare filescmp file1 file2
cmpenvCompare with env expansioncmpenv file1 file2
grepSearch in filegrep 'pattern' file
catPrint file contentscat file
cdChange directorycd subdir
cpCopy filescp src dst
mvMove/rename filesmv old new
rmRemove filesrm file
mkdirCreate directoriesmkdir dir
existsCheck file existenceexists file
envSet/get environmentenv KEY=value
echoPrint to stdout bufferecho text
sleepWait for durationsleep 1s
stopStop test (non-error)stop 'reason'
skipSkip testskip 'reason'
helpList commandshelp

Prefixes

  • ! - Command must fail (exit code ≠ 0)
  • ? - Command may succeed or fail
  • No prefix - Command must succeed (exit code = 0)

Example:

exec mytool --valid # Must succeed
! exec mytool --invalid # Must fail
? exec mytool --unpredictable # Either is OK

Conditions

Conditional execution based on platform:

[unix] exec unix-tool
[windows] exec windows-tool
[linux] exec linux-tool
[amd64] exec x86_64-tool
[!windows] exec not-windows-tool
[exec:python] exec python-script

Environment Variables

Built-in Variables

  • $WORK or $TMPDIR - Temporary working directory
  • $PWD - Current working directory
  • ${/} - OS path separator (/ or \)
  • ${:} - Path list separator (: or ;)

Usage

env KEY=value
echo $KEY

In comparison:

cmp stdout $WORK/expected.txt

Virtual Files

stdout and stderr can be used as virtual files:

exec mytool
cmp stdout expected.txt
cp stderr debug.log

Advanced Usage

Background Processes

exec server &
sleep 2s
exec client --request
wait

Multi-line Matching

exec mytool
stdout 'line1.*line2.*line3'

File Comparison with Diff

exec mytool input.txt
cmp stdout output.txt

If files differ, unified diff is shown.

Environment Expansion

env NAME=World
echo "Hello $NAME"

In cmpenv, environment variables are expanded before comparison.

API Usage

Running Tests

use emx_testspec::{TestRunner,RunConfig};let config = RunConfig{dir:"tests".into(),filter:None,workdir_root:None,preserve_work:false,verbose:false,extensions:vec![".txtar".into()],setup:None,};let runner = TestRunner::new(config);let result = runner.run_all()?;assert!(result.all_passed());

Custom Commands

use emx_testspec::{Engine,Cmd,CmdResult};use anyhow::Result;structMyCustomCmd;implCmdforMyCustomCmd{fnusage(&self) -> (String,String){("mycmd".into(),"[args]".into())}fnrun(&self,args:&[String],state:&mutState) -> Result<CmdResult>{// Custom logic hereOk(CmdResult::Success)}}// Register custom commandletmut engine = Engine::new();
engine.register_command("mycmd",Box::new(MyCustomCmd));

Environment Variables for Testing

VariableEffect
TESTSCRIPT_VERBOSE=1Enable verbose logging
TESTSCRIPT_WORK=1Preserve working directories

Format Specification

See spec/00-princ-11-tool-testspec.mx for the full grammar specification.

BNF Grammar

script-line = prefix? condition* command-name arg* inline-comment?
prefix = "!" | "?"
condition = "[" "!"? cond-name (":" suffix)? "]"
command = identifier
arg = quoted-arg | unquoted-arg
quoted-arg = "'" (char | "''")* "'"

Documentation

License

MIT License - see LICENSE file for details.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Related Projects

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

13 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

emx-testspec

A testspec engine for CLI E2E testing, inspired by Go's cmd/internal/script and roguepeppe/go-internal/testscript.

Overview

emx-testspec provides a declarative scripting DSL for testing CLI tools. Tests are written as txtar archives where the comment section contains script commands and the file section provides test fixtures.

Features

  • Declarative syntax - Simple, readable test scripts
  • Txtar integration - Test fixtures in the same file
  • Rich command set - exec, stdout, stderr, cmp, grep, etc.
  • Platform conditions - Conditional execution per OS/arch
  • Virtual files - stdout and stderr as file-like objects
  • Background processes - Run commands asynchronously
  • Flexible matching - Regex patterns with multi-line support
  • MIT License - Free to use in any project

Installation

Add to your Cargo.toml:

[dependencies]
emx-testspec = "0.1"

Or use via Git:

[dependencies]
emx-testspec = { git = "https://github.com/coreseekdev/emx-testspec" }

Requires emx-txtar as a dependency.

Quick Start

Basic Test

Create tests/mytool.txtar:

# Test basic functionality
exec mytool input.txt
stdout 'success'
-- input.txt --
test data

Run tests:

use emx_testspec::run_and_assert;#[test]fntest_mytool(){run_and_assert("tests");}

Using as CLI

Install the CLI:

cargo install emx-testspec --git https://github.com/coreseekdev/emx-testspec

Run tests:

emx-testspec tests/ # Run all tests
emx-testspec tests/test.txtar # Run single test
emx-testspec tests/ -v # Verbose output
emx-testspec tests/ -f "basic"# Filter by name
emx-testspec tests/ --keep # Preserve work directories

Script Syntax

Commands

CommandDescriptionExample
execExecute a commandexec mytool arg1 arg2
stdoutMatch stdout with regexstdout 'expected'
stderrMatch stderr with regexstderr 'error message'
cmpCompare filescmp file1 file2
cmpenvCompare with env expansioncmpenv file1 file2
grepSearch in filegrep 'pattern' file
catPrint file contentscat file
cdChange directorycd subdir
cpCopy filescp src dst
mvMove/rename filesmv old new
rmRemove filesrm file
mkdirCreate directoriesmkdir dir
existsCheck file existenceexists file
envSet/get environmentenv KEY=value
echoPrint to stdout bufferecho text
sleepWait for durationsleep 1s
stopStop test (non-error)stop 'reason'
skipSkip testskip 'reason'
helpList commandshelp

Prefixes

  • ! - Command must fail (exit code ≠ 0)
  • ? - Command may succeed or fail
  • No prefix - Command must succeed (exit code = 0)

Example:

exec mytool --valid # Must succeed
! exec mytool --invalid # Must fail
? exec mytool --unpredictable # Either is OK

Conditions

Conditional execution based on platform:

[unix] exec unix-tool
[windows] exec windows-tool
[linux] exec linux-tool
[amd64] exec x86_64-tool
[!windows] exec not-windows-tool
[exec:python] exec python-script

Environment Variables

Built-in Variables

  • $WORK or $TMPDIR - Temporary working directory
  • $PWD - Current working directory
  • ${/} - OS path separator (/ or \)
  • ${:} - Path list separator (: or ;)

Usage

env KEY=value
echo $KEY

In comparison:

cmp stdout $WORK/expected.txt

Virtual Files

stdout and stderr can be used as virtual files:

exec mytool
cmp stdout expected.txt
cp stderr debug.log

Advanced Usage

Background Processes

exec server &
sleep 2s
exec client --request
wait

Multi-line Matching

exec mytool
stdout 'line1.*line2.*line3'

File Comparison with Diff

exec mytool input.txt
cmp stdout output.txt

If files differ, unified diff is shown.

Environment Expansion

env NAME=World
echo "Hello $NAME"

In cmpenv, environment variables are expanded before comparison.

API Usage

Running Tests

use emx_testspec::{TestRunner,RunConfig};let config = RunConfig{dir:"tests".into(),filter:None,workdir_root:None,preserve_work:false,verbose:false,extensions:vec![".txtar".into()],setup:None,};let runner = TestRunner::new(config);let result = runner.run_all()?;assert!(result.all_passed());

Custom Commands

use emx_testspec::{Engine,Cmd,CmdResult};use anyhow::Result;structMyCustomCmd;implCmdforMyCustomCmd{fnusage(&self) -> (String,String){("mycmd".into(),"[args]".into())}fnrun(&self,args:&[String],state:&mutState) -> Result<CmdResult>{// Custom logic hereOk(CmdResult::Success)}}// Register custom commandletmut engine = Engine::new();
engine.register_command("mycmd",Box::new(MyCustomCmd));

Environment Variables for Testing

VariableEffect
TESTSCRIPT_VERBOSE=1Enable verbose logging
TESTSCRIPT_WORK=1Preserve working directories

Format Specification

See spec/00-princ-11-tool-testspec.mx for the full grammar specification.

BNF Grammar

script-line = prefix? condition* command-name arg* inline-comment?
prefix = "!" | "?"
condition = "[" "!"? cond-name (":" suffix)? "]"
command = identifier
arg = quoted-arg | unquoted-arg
quoted-arg = "'" (char | "''")* "'"

Documentation

License

MIT License - see LICENSE file for details.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Related Projects

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

13 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

emx-testspec

A testspec engine for CLI E2E testing, inspired by Go's cmd/internal/script and roguepeppe/go-internal/testscript.

Overview

emx-testspec provides a declarative scripting DSL for testing CLI tools. Tests are written as txtar archives where the comment section contains script commands and the file section provides test fixtures.

Features

  • Declarative syntax - Simple, readable test scripts
  • Txtar integration - Test fixtures in the same file
  • Rich command set - exec, stdout, stderr, cmp, grep, etc.
  • Platform conditions - Conditional execution per OS/arch
  • Virtual files - stdout and stderr as file-like objects
  • Background processes - Run commands asynchronously
  • Flexible matching - Regex patterns with multi-line support
  • MIT License - Free to use in any project

Installation

Add to your Cargo.toml:

[dependencies]
emx-testspec = "0.1"

Or use via Git:

[dependencies]
emx-testspec = { git = "https://github.com/coreseekdev/emx-testspec" }

Requires emx-txtar as a dependency.

Quick Start

Basic Test

Create tests/mytool.txtar:

# Test basic functionality
exec mytool input.txt
stdout 'success'
-- input.txt --
test data

Run tests:

use emx_testspec::run_and_assert;#[test]fntest_mytool(){run_and_assert("tests");}

Using as CLI

Install the CLI:

cargo install emx-testspec --git https://github.com/coreseekdev/emx-testspec

Run tests:

emx-testspec tests/ # Run all tests
emx-testspec tests/test.txtar # Run single test
emx-testspec tests/ -v # Verbose output
emx-testspec tests/ -f "basic"# Filter by name
emx-testspec tests/ --keep # Preserve work directories

Script Syntax

Commands

CommandDescriptionExample
execExecute a commandexec mytool arg1 arg2
stdoutMatch stdout with regexstdout 'expected'
stderrMatch stderr with regexstderr 'error message'
cmpCompare filescmp file1 file2
cmpenvCompare with env expansioncmpenv file1 file2
grepSearch in filegrep 'pattern' file
catPrint file contentscat file
cdChange directorycd subdir
cpCopy filescp src dst
mvMove/rename filesmv old new
rmRemove filesrm file
mkdirCreate directoriesmkdir dir
existsCheck file existenceexists file
envSet/get environmentenv KEY=value
echoPrint to stdout bufferecho text
sleepWait for durationsleep 1s
stopStop test (non-error)stop 'reason'
skipSkip testskip 'reason'
helpList commandshelp

Prefixes

  • ! - Command must fail (exit code ≠ 0)
  • ? - Command may succeed or fail
  • No prefix - Command must succeed (exit code = 0)

Example:

exec mytool --valid # Must succeed
! exec mytool --invalid # Must fail
? exec mytool --unpredictable # Either is OK

Conditions

Conditional execution based on platform:

[unix] exec unix-tool
[windows] exec windows-tool
[linux] exec linux-tool
[amd64] exec x86_64-tool
[!windows] exec not-windows-tool
[exec:python] exec python-script

Environment Variables

Built-in Variables

  • $WORK or $TMPDIR - Temporary working directory
  • $PWD - Current working directory
  • ${/} - OS path separator (/ or \)
  • ${:} - Path list separator (: or ;)

Usage

env KEY=value
echo $KEY

In comparison:

cmp stdout $WORK/expected.txt

Virtual Files

stdout and stderr can be used as virtual files:

exec mytool
cmp stdout expected.txt
cp stderr debug.log

Advanced Usage

Background Processes

exec server &
sleep 2s
exec client --request
wait

Multi-line Matching

exec mytool
stdout 'line1.*line2.*line3'

File Comparison with Diff

exec mytool input.txt
cmp stdout output.txt

If files differ, unified diff is shown.

Environment Expansion

env NAME=World
echo "Hello $NAME"

In cmpenv, environment variables are expanded before comparison.

API Usage

Running Tests

use emx_testspec::{TestRunner,RunConfig};let config = RunConfig{dir:"tests".into(),filter:None,workdir_root:None,preserve_work:false,verbose:false,extensions:vec![".txtar".into()],setup:None,};let runner = TestRunner::new(config);let result = runner.run_all()?;assert!(result.all_passed());

Custom Commands

use emx_testspec::{Engine,Cmd,CmdResult};use anyhow::Result;structMyCustomCmd;implCmdforMyCustomCmd{fnusage(&self) -> (String,String){("mycmd".into(),"[args]".into())}fnrun(&self,args:&[String],state:&mutState) -> Result<CmdResult>{// Custom logic hereOk(CmdResult::Success)}}// Register custom commandletmut engine = Engine::new();
engine.register_command("mycmd",Box::new(MyCustomCmd));

Environment Variables for Testing

VariableEffect
TESTSCRIPT_VERBOSE=1Enable verbose logging
TESTSCRIPT_WORK=1Preserve working directories

Format Specification

See spec/00-princ-11-tool-testspec.mx for the full grammar specification.

BNF Grammar

script-line = prefix? condition* command-name arg* inline-comment?
prefix = "!" | "?"
condition = "[" "!"? cond-name (":" suffix)? "]"
command = identifier
arg = quoted-arg | unquoted-arg
quoted-arg = "'" (char | "''")* "'"

Documentation

License

MIT License - see LICENSE file for details.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Related Projects

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Latest commit

History

13 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

emx-testspec

A testspec engine for CLI E2E testing, inspired by Go's cmd/internal/script and roguepeppe/go-internal/testscript.

Overview

emx-testspec provides a declarative scripting DSL for testing CLI tools. Tests are written as txtar archives where the comment section contains script commands and the file section provides test fixtures.

Features

  • Declarative syntax - Simple, readable test scripts
  • Txtar integration - Test fixtures in the same file
  • Rich command set - exec, stdout, stderr, cmp, grep, etc.
  • Platform conditions - Conditional execution per OS/arch
  • Virtual files - stdout and stderr as file-like objects
  • Background processes - Run commands asynchronously
  • Flexible matching - Regex patterns with multi-line support
  • MIT License - Free to use in any project

Installation

Add to your Cargo.toml:

[dependencies]
emx-testspec = "0.1"

Or use via Git:

[dependencies]
emx-testspec = { git = "https://github.com/coreseekdev/emx-testspec" }

Requires emx-txtar as a dependency.

Quick Start

Basic Test

Create tests/mytool.txtar:

# Test basic functionality
exec mytool input.txt
stdout 'success'
-- input.txt --
test data

Run tests:

use emx_testspec::run_and_assert;#[test]fntest_mytool(){run_and_assert("tests");}

Using as CLI

Install the CLI:

cargo install emx-testspec --git https://github.com/coreseekdev/emx-testspec

Run tests:

emx-testspec tests/ # Run all tests
emx-testspec tests/test.txtar # Run single test
emx-testspec tests/ -v # Verbose output
emx-testspec tests/ -f "basic"# Filter by name
emx-testspec tests/ --keep # Preserve work directories

Script Syntax

Commands

CommandDescriptionExample
execExecute a commandexec mytool arg1 arg2
stdoutMatch stdout with regexstdout 'expected'
stderrMatch stderr with regexstderr 'error message'
cmpCompare filescmp file1 file2
cmpenvCompare with env expansioncmpenv file1 file2
grepSearch in filegrep 'pattern' file
catPrint file contentscat file
cdChange directorycd subdir
cpCopy filescp src dst
mvMove/rename filesmv old new
rmRemove filesrm file
mkdirCreate directoriesmkdir dir
existsCheck file existenceexists file
envSet/get environmentenv KEY=value
echoPrint to stdout bufferecho text
sleepWait for durationsleep 1s
stopStop test (non-error)stop 'reason'
skipSkip testskip 'reason'
helpList commandshelp

Prefixes

  • ! - Command must fail (exit code ≠ 0)
  • ? - Command may succeed or fail
  • No prefix - Command must succeed (exit code = 0)

Example:

exec mytool --valid # Must succeed
! exec mytool --invalid # Must fail
? exec mytool --unpredictable # Either is OK

Conditions

Conditional execution based on platform:

[unix] exec unix-tool
[windows] exec windows-tool
[linux] exec linux-tool
[amd64] exec x86_64-tool
[!windows] exec not-windows-tool
[exec:python] exec python-script

Environment Variables

Built-in Variables

  • $WORK or $TMPDIR - Temporary working directory
  • $PWD - Current working directory
  • ${/} - OS path separator (/ or \)
  • ${:} - Path list separator (: or ;)

Usage

env KEY=value
echo $KEY

In comparison:

cmp stdout $WORK/expected.txt

Virtual Files

stdout and stderr can be used as virtual files:

exec mytool
cmp stdout expected.txt
cp stderr debug.log

Advanced Usage

Background Processes

exec server &
sleep 2s
exec client --request
wait

Multi-line Matching

exec mytool
stdout 'line1.*line2.*line3'

File Comparison with Diff

exec mytool input.txt
cmp stdout output.txt

If files differ, unified diff is shown.

Environment Expansion

env NAME=World
echo "Hello $NAME"

In cmpenv, environment variables are expanded before comparison.

API Usage

Running Tests

use emx_testspec::{TestRunner,RunConfig};let config = RunConfig{dir:"tests".into(),filter:None,workdir_root:None,preserve_work:false,verbose:false,extensions:vec![".txtar".into()],setup:None,};let runner = TestRunner::new(config);let result = runner.run_all()?;assert!(result.all_passed());

Custom Commands

use emx_testspec::{Engine,Cmd,CmdResult};use anyhow::Result;structMyCustomCmd;implCmdforMyCustomCmd{fnusage(&self) -> (String,String){("mycmd".into(),"[args]".into())}fnrun(&self,args:&[String],state:&mutState) -> Result<CmdResult>{// Custom logic hereOk(CmdResult::Success)}}// Register custom commandletmut engine = Engine::new();
engine.register_command("mycmd",Box::new(MyCustomCmd));

Environment Variables for Testing

VariableEffect
TESTSCRIPT_VERBOSE=1Enable verbose logging
TESTSCRIPT_WORK=1Preserve working directories

Format Specification

See spec/00-princ-11-tool-testspec.mx for the full grammar specification.

BNF Grammar

script-line = prefix? condition* command-name arg* inline-comment?
prefix = "!" | "?"
condition = "[" "!"? cond-name (":" suffix)? "]"
command = identifier
arg = quoted-arg | unquoted-arg
quoted-arg = "'" (char | "''")* "'"

Documentation

License

MIT License - see LICENSE file for details.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Related Projects

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages