ace-test-runner 0.26.0 → 0.27.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -0
- data/README.md +11 -0
- data/lib/ace/test_runner/atoms/environment_sanitizer.rb +33 -0
- data/lib/ace/test_runner/cli/commands/test.rb +11 -0
- data/lib/ace/test_runner/models/environment_policy.rb +143 -0
- data/lib/ace/test_runner/models/test_configuration.rb +4 -2
- data/lib/ace/test_runner/molecules/config_loader.rb +1 -0
- data/lib/ace/test_runner/molecules/fixture_environment.rb +90 -0
- data/lib/ace/test_runner/molecules/in_process_runner.rb +27 -10
- data/lib/ace/test_runner/molecules/smart_test_executor.rb +3 -3
- data/lib/ace/test_runner/molecules/test_executor.rb +10 -13
- data/lib/ace/test_runner/organisms/test_orchestrator.rb +38 -2
- data/lib/ace/test_runner/rake_task.rb +17 -7
- data/lib/ace/test_runner/suite/orchestrator.rb +10 -1
- data/lib/ace/test_runner/suite/process_monitor.rb +34 -9
- data/lib/ace/test_runner/version.rb +1 -1
- data/lib/ace/test_runner.rb +9 -2
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8b9e68c09f883cccd0d324be3b33f54de394293a8b0a4e7421ed697b8dffff7e
|
|
4
|
+
data.tar.gz: 92f8e49cccbf575821c3195bcdc92b7cf393c728474d7d116f77f3f94ad3fcae
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0dd0f2a17a9bf8f99bc4fd6c3a7004d293a0f43a1838c0654bd98743e2277d734f42043cb2a56a3c7e0e994aa863c4ab3fac0771ae7709f28dadb7ed3ebd46fb
|
|
7
|
+
data.tar.gz: cca845831f57a5b706dd547560c351ea30d19e17b99d838a8f0e1f7fadb157fd14eb35aa08a60715a49d3a24e02193e638135c075ecbb79e52fa23e493a4a9ac
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.27.1] - 2026-09-29
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- Suite children now launch with the suite's own Ruby binary plus `-rbundler/setup`, so bundler discovers the checkout Gemfile by walking up from each package directory. This gives hermetic children a deterministic source-of-truth dependency surface instead of silently relying on PATH-resolved interpreters (which can fail through manager shims under fixture HOME/XDG overrides) or on globally installed ace gems. Closes the runner-level workspace LOAD_PATH follow-up from the hermetic-tests line.
|
|
14
|
+
|
|
15
|
+
## [0.27.0] - 2026-09-28
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- Hermetic test environments by default: deterministic `ace-test` and `ace-test-suite` runs launch every test child (serial, parallel, nested, in-process, suite workers) from a documented minimal environment allowlist with fixture-owned `HOME`/XDG directories, never inheriting ambient `LAB_*`, ACE/runtime/provider, or user configuration.
|
|
19
|
+
- Fixture override configuration (`environment.preserve`/`overrides`/`require` in runner or suite configuration) with deterministic setup errors naming missing keys without exposing values. Suite-level `test_suite.environment` configuration propagates to package workers; package-level configuration wins for the same key.
|
|
20
|
+
- Deterministic/live mode labeling in CLI output; live integration remains opt-in through E2E entrypoints.
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
- Test subprocess launching used environment merging, so ambient `LAB_*`/`ACE_*`/provider variables leaked into test children; child environments are now the hermetic fixture environment exactly (`unsetenv_others`).
|
|
24
|
+
- `ace-test-suite` resolves `ace-test` co-located with the suite itself instead of an ambient PATH selection.
|
|
25
|
+
- Rake task integration loads in a fresh process without the runner preloaded.
|
|
26
|
+
- Failed fixture environment builds clean up their temporary root before raising.
|
|
27
|
+
|
|
10
28
|
## [0.26.0] - 2026-09-27
|
|
11
29
|
|
|
12
30
|
### Added
|
data/README.md
CHANGED
|
@@ -26,6 +26,17 @@
|
|
|
26
26
|
2. Execute the matching tests with failure-oriented output and optional profiling of the slowest cases.
|
|
27
27
|
3. Persist structured run reports for historical debugging and searchable triage across runs.
|
|
28
28
|
|
|
29
|
+
## Hermetic by Default
|
|
30
|
+
|
|
31
|
+
Package tests are deterministic: every test child starts from a documented minimal environment, never from ambient `LAB_*`, ACE/runtime/provider configuration, or user configuration. Output labels the mode (`Test mode: deterministic (hermetic environment)`). Live integration remains opt-in through the E2E entrypoints with explicit target configuration.
|
|
32
|
+
|
|
33
|
+
- Preserved from the parent: `PATH`, `LANG`, `LC_ALL`, `TMPDIR`, `TMP`, `TEMP`.
|
|
34
|
+
- Replaced with test-owned fixture directories: `HOME`, `XDG_CONFIG_HOME`, `XDG_CACHE_HOME`, `XDG_DATA_HOME`.
|
|
35
|
+
- Everything else (ambient Lab/runtime sockets, provider credentials, proxy and project-selection variables) never reaches tests.
|
|
36
|
+
- Fixture-specific values are supplied explicitly through runner configuration (`environment.overrides` in `.ace/test/runner.yml`); required values that are missing produce a setup error naming the key. The invoking environment is never mutated.
|
|
37
|
+
|
|
38
|
+
See the [usage guide](docs/usage.md#hermetic-environment-contract) for the full contract and configuration examples.
|
|
39
|
+
|
|
29
40
|
## Use Cases
|
|
30
41
|
|
|
31
42
|
**Run package tests from anywhere in the monorepo** - execute [`ace-test [package]`](docs/usage.md) by name without changing directories, keeping outputs consistent across local and CI environments.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ace
|
|
4
|
+
module TestRunner
|
|
5
|
+
module Atoms
|
|
6
|
+
# Pure allowlist filter from a parent environment snapshot to the
|
|
7
|
+
# deterministic child base environment.
|
|
8
|
+
#
|
|
9
|
+
# Copies only policy-preserved keys into a fresh hash. Fixture-home
|
|
10
|
+
# replacements and fixture overrides are applied later by
|
|
11
|
+
# Molecules::FixtureEnvironment. Never mutates ENV or the input hash.
|
|
12
|
+
module EnvironmentSanitizer
|
|
13
|
+
module_function
|
|
14
|
+
|
|
15
|
+
# @param parent_env [Hash] snapshot of the parent environment (not modified)
|
|
16
|
+
# @param policy [Models::EnvironmentPolicy] hermetic environment contract
|
|
17
|
+
# @return [Hash] new child base environment containing only preserved keys
|
|
18
|
+
def sanitize(parent_env, policy)
|
|
19
|
+
parent_env.each_with_object({}) do |(key, value), child|
|
|
20
|
+
next if value.nil?
|
|
21
|
+
|
|
22
|
+
child[key] = value if policy.preserved_key?(key)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Names of parent keys dropped by the policy (diagnostics only).
|
|
27
|
+
def dropped_keys(parent_env, policy)
|
|
28
|
+
parent_env.keys.reject { |key| policy.preserved_key?(key) }
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -147,6 +147,11 @@ module Ace
|
|
|
147
147
|
raise Ace::Support::Cli::Error.new(e.message)
|
|
148
148
|
end
|
|
149
149
|
|
|
150
|
+
# Label the execution mode: package tests are always deterministic
|
|
151
|
+
# (hermetic fixture environment). Live integration runs only through
|
|
152
|
+
# the E2E entrypoints with explicit target configuration.
|
|
153
|
+
display_deterministic_mode(test_options)
|
|
154
|
+
|
|
150
155
|
# Run tests with special exit! handling for Minitest compatibility
|
|
151
156
|
run_tests_with_exit_handling(test_options)
|
|
152
157
|
rescue Ace::TestRunner::Error => e
|
|
@@ -311,6 +316,12 @@ module Ace
|
|
|
311
316
|
exit_code
|
|
312
317
|
end
|
|
313
318
|
|
|
319
|
+
def display_deterministic_mode(options)
|
|
320
|
+
return if options[:quiet]
|
|
321
|
+
|
|
322
|
+
puts "Test mode: deterministic (hermetic environment)"
|
|
323
|
+
end
|
|
324
|
+
|
|
314
325
|
def display_config_summary(options)
|
|
315
326
|
return if options[:quiet]
|
|
316
327
|
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Ace
|
|
6
|
+
module TestRunner
|
|
7
|
+
# Deterministic setup failure: names the missing or invalid key, never its value.
|
|
8
|
+
class EnvironmentSetupError < Error; end
|
|
9
|
+
|
|
10
|
+
module Models
|
|
11
|
+
# Immutable declaration of the hermetic subprocess environment contract.
|
|
12
|
+
#
|
|
13
|
+
# Deterministic test children start from an empty environment and receive only:
|
|
14
|
+
# - preserved toolchain/process keys (PATH, locale, temp directories)
|
|
15
|
+
# - fixture-owned HOME/XDG replacements (applied by Molecules::FixtureEnvironment)
|
|
16
|
+
# - explicit fixture overrides supplied through runner configuration
|
|
17
|
+
#
|
|
18
|
+
# Everything else - ambient LAB_* and ACE_* runtime/provider configuration,
|
|
19
|
+
# credentials, sockets, proxy and project-selection variables - never reaches
|
|
20
|
+
# a deterministic test child.
|
|
21
|
+
class EnvironmentPolicy
|
|
22
|
+
# Runner-internal channel for ace-test-suite to propagate its suite-level
|
|
23
|
+
# fixture configuration into package workers. Consumed by the runner when
|
|
24
|
+
# building the package policy; never passed to test children (the blocked
|
|
25
|
+
# ACE_ prefix keeps it out of every sanitized child environment).
|
|
26
|
+
SUITE_CHANNEL_KEY = "ACE_TEST_FIXTURE_ENVIRONMENT"
|
|
27
|
+
|
|
28
|
+
# Toolchain/process keys preserved from the parent environment.
|
|
29
|
+
PRESERVED_KEYS = %w[PATH LANG LC_ALL TMPDIR TMP TEMP].freeze
|
|
30
|
+
|
|
31
|
+
# Key prefixes never allowed in deterministic children or preserve lists.
|
|
32
|
+
BLOCKED_PREFIXES = %w[
|
|
33
|
+
ACE_ ANTHROPIC_ AWS_ AZURE_ BUNDLE_ COHERE_ DEEPSEEK_ FIREWORKS_ GEM_
|
|
34
|
+
GEMINI_ GLM_ GOOGLE_ GROQ_ HF_ HUGGINGFACE_ LAB_ MISTRAL_ MOONSHOT_
|
|
35
|
+
OPENAI_ OPENROUTER_ TOGETHER_ XAI_ ZAI_ ZHIPU_
|
|
36
|
+
].freeze
|
|
37
|
+
|
|
38
|
+
# Exact keys never allowed in deterministic children or preserve lists.
|
|
39
|
+
BLOCKED_KEYS = %w[
|
|
40
|
+
ALL_PROXY BASH_ENV DATABASE_URL DOCKER_HOST ENV GITHUB_TOKEN
|
|
41
|
+
GITLAB_TOKEN HTTP_PROXY HTTPS_PROXY NO_PROXY PYTHONPATH
|
|
42
|
+
PYTHONSTARTUP RUBYLIB RUBYOPT SSH_AUTH_SOCK
|
|
43
|
+
all_proxy http_proxy https_proxy no_proxy
|
|
44
|
+
].freeze
|
|
45
|
+
|
|
46
|
+
# Keys replaced with fixture-owned directories instead of parent values.
|
|
47
|
+
FIXTURE_HOME_KEYS = %w[HOME XDG_CONFIG_HOME XDG_CACHE_HOME XDG_DATA_HOME].freeze
|
|
48
|
+
|
|
49
|
+
attr_reader :preserved_keys, :blocked_prefixes, :blocked_keys,
|
|
50
|
+
:fixture_home_keys, :overrides, :required_keys
|
|
51
|
+
|
|
52
|
+
def initialize(preserved_keys: PRESERVED_KEYS, blocked_prefixes: BLOCKED_PREFIXES,
|
|
53
|
+
blocked_keys: BLOCKED_KEYS, fixture_home_keys: FIXTURE_HOME_KEYS,
|
|
54
|
+
overrides: {}, required_keys: [])
|
|
55
|
+
@preserved_keys = preserved_keys.map(&:to_s).freeze
|
|
56
|
+
@blocked_prefixes = blocked_prefixes.map(&:to_s).freeze
|
|
57
|
+
@blocked_keys = blocked_keys.map(&:to_s).freeze
|
|
58
|
+
@fixture_home_keys = fixture_home_keys.map(&:to_s).freeze
|
|
59
|
+
@overrides = overrides.each_with_object({}) { |(key, value), hash| hash[key.to_s] = value.to_s }.freeze
|
|
60
|
+
@required_keys = required_keys.map(&:to_s).freeze
|
|
61
|
+
validate_contract!
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Build a policy from runner or suite configuration:
|
|
65
|
+
#
|
|
66
|
+
# environment:
|
|
67
|
+
# preserve: [EXTRA_TOOLCHAIN_KEY]
|
|
68
|
+
# overrides:
|
|
69
|
+
# MY_ENDPOINT: "http://127.0.0.1:1"
|
|
70
|
+
# require: [MY_ENDPOINT]
|
|
71
|
+
def self.from_config(config)
|
|
72
|
+
normalized = (config || {}).transform_keys(&:to_sym)
|
|
73
|
+
new(
|
|
74
|
+
preserved_keys: PRESERVED_KEYS + Array(normalized[:preserve]),
|
|
75
|
+
overrides: normalized[:overrides] || {},
|
|
76
|
+
required_keys: Array(normalized[:require])
|
|
77
|
+
)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Build a policy from the suite propagation channel payload.
|
|
81
|
+
def self.from_channel(raw)
|
|
82
|
+
from_config(JSON.parse(raw, symbolize_names: true))
|
|
83
|
+
rescue JSON::ParserError => e
|
|
84
|
+
raise EnvironmentSetupError, "Invalid #{SUITE_CHANNEL_KEY} payload: #{e.message}"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Combine this (broader, e.g. suite-level) policy with a more specific one
|
|
88
|
+
# (e.g. package runner configuration). Specific overrides win; preserve and
|
|
89
|
+
# require lists union.
|
|
90
|
+
def merge_with(more_specific)
|
|
91
|
+
self.class.new(
|
|
92
|
+
preserved_keys: (preserved_keys + more_specific.preserved_keys).uniq,
|
|
93
|
+
overrides: overrides.merge(more_specific.overrides),
|
|
94
|
+
required_keys: (required_keys + more_specific.required_keys).uniq
|
|
95
|
+
)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Channel payload for propagating this policy's configuration extras.
|
|
99
|
+
def channel_payload
|
|
100
|
+
JSON.generate(
|
|
101
|
+
overrides: overrides,
|
|
102
|
+
require: required_keys,
|
|
103
|
+
preserve: preserved_keys - PRESERVED_KEYS
|
|
104
|
+
)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def preserved_key?(key)
|
|
108
|
+
@preserved_keys.include?(key)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def blocked_key?(key)
|
|
112
|
+
@blocked_keys.include?(key) || @blocked_prefixes.any? { |prefix| key.start_with?(prefix) }
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def credential_like?(key)
|
|
116
|
+
key.match?(/token|secret|password|passwd|credential|api_?key|_key\z|private_?key|_sock(?:et)?\z|_url\z|_endpoint\z/i)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Diagnostic-safe value for a key; credential-like values are never exposed.
|
|
120
|
+
def redacted_value(key, value)
|
|
121
|
+
credential_like?(key) ? "[redacted]" : value
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
private
|
|
125
|
+
|
|
126
|
+
def validate_contract!
|
|
127
|
+
@preserved_keys.each do |key|
|
|
128
|
+
next unless blocked_key?(key)
|
|
129
|
+
|
|
130
|
+
raise EnvironmentSetupError,
|
|
131
|
+
"Environment policy: cannot preserve blocked ambient key '#{key}'"
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
@required_keys.each do |key|
|
|
135
|
+
next unless key.empty?
|
|
136
|
+
|
|
137
|
+
raise EnvironmentSetupError, "Environment policy: required key names must not be empty"
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
@@ -9,7 +9,7 @@ module Ace
|
|
|
9
9
|
:verbose, :filter, :fix_deprecations, :patterns,
|
|
10
10
|
:timeout, :parallel, :color, :per_file, :targets,
|
|
11
11
|
:target, :config_path, :failure_limits, :profile,
|
|
12
|
-
:execution, :files, :run_in_single_batch
|
|
12
|
+
:execution, :files, :run_in_single_batch, :environment
|
|
13
13
|
|
|
14
14
|
def initialize(attributes = {})
|
|
15
15
|
@format = attributes[:format] || "progress" # Default to per-test progress
|
|
@@ -32,6 +32,7 @@ module Ace
|
|
|
32
32
|
@execution = attributes[:execution] || {}
|
|
33
33
|
@files = attributes[:files] # Specific files to test (overrides target/patterns)
|
|
34
34
|
@run_in_single_batch = attributes[:run_in_single_batch] || false
|
|
35
|
+
@environment = attributes[:environment] || {} # Hermetic fixture environment configuration
|
|
35
36
|
end
|
|
36
37
|
|
|
37
38
|
def valid_format?
|
|
@@ -94,7 +95,8 @@ module Ace
|
|
|
94
95
|
profile: profile,
|
|
95
96
|
execution: execution,
|
|
96
97
|
files: files,
|
|
97
|
-
run_in_single_batch: run_in_single_batch
|
|
98
|
+
run_in_single_batch: run_in_single_batch,
|
|
99
|
+
environment: environment
|
|
98
100
|
}
|
|
99
101
|
end
|
|
100
102
|
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "tmpdir"
|
|
5
|
+
|
|
6
|
+
module Ace
|
|
7
|
+
module TestRunner
|
|
8
|
+
module Molecules
|
|
9
|
+
# Builds a launch-ready hermetic environment for deterministic test children.
|
|
10
|
+
#
|
|
11
|
+
# The fixture environment:
|
|
12
|
+
# - starts from the sanitized allowlisted base (Atoms::EnvironmentSanitizer)
|
|
13
|
+
# - replaces HOME and XDG locations with test-owned directories
|
|
14
|
+
# - applies explicit fixture overrides after sanitization
|
|
15
|
+
# - validates required explicit configuration, naming missing keys not values
|
|
16
|
+
#
|
|
17
|
+
# Cleanup owns only the temporary root created here; the parent home,
|
|
18
|
+
# project directories, and system temp locations are never touched.
|
|
19
|
+
class FixtureEnvironment
|
|
20
|
+
attr_reader :policy, :root, :env
|
|
21
|
+
|
|
22
|
+
def initialize(policy:, parent_env:, root_factory: Dir.method(:mktmpdir))
|
|
23
|
+
@policy = policy
|
|
24
|
+
@parent_env = parent_env
|
|
25
|
+
@root_factory = root_factory
|
|
26
|
+
@root = nil
|
|
27
|
+
@env = nil
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Build the fixture environment. Returns self for chaining.
|
|
31
|
+
# A failed build cleans up the temporary root it created before raising.
|
|
32
|
+
def build
|
|
33
|
+
@root = @root_factory.call("ace-test-fixture-")
|
|
34
|
+
@env = build_environment
|
|
35
|
+
self
|
|
36
|
+
rescue StandardError
|
|
37
|
+
cleanup
|
|
38
|
+
raise
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Remove the test-owned fixture root. Safe to call multiple times.
|
|
42
|
+
def cleanup
|
|
43
|
+
return unless @root
|
|
44
|
+
|
|
45
|
+
FileUtils.rm_rf(@root)
|
|
46
|
+
@root = nil
|
|
47
|
+
@env = nil
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def build_environment
|
|
53
|
+
env = Atoms::EnvironmentSanitizer.sanitize(@parent_env, @policy)
|
|
54
|
+
env["MT_NO_AUTORUN"] = "1"
|
|
55
|
+
apply_fixture_homes(env)
|
|
56
|
+
@policy.overrides.each { |key, value| env[key] = value }
|
|
57
|
+
validate_required_keys!(env)
|
|
58
|
+
env
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def apply_fixture_homes(env)
|
|
62
|
+
home = File.join(@root, "home")
|
|
63
|
+
config_home = File.join(home, ".config")
|
|
64
|
+
cache_home = File.join(home, ".cache")
|
|
65
|
+
data_home = File.join(home, ".local", "share")
|
|
66
|
+
|
|
67
|
+
[home, config_home, cache_home, data_home].each { |dir| FileUtils.mkdir_p(dir) }
|
|
68
|
+
|
|
69
|
+
@policy.fixture_home_keys.each do |key|
|
|
70
|
+
env[key] = case key
|
|
71
|
+
when "HOME" then home
|
|
72
|
+
when "XDG_CONFIG_HOME" then config_home
|
|
73
|
+
when "XDG_CACHE_HOME" then cache_home
|
|
74
|
+
when "XDG_DATA_HOME" then data_home
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def validate_required_keys!(env)
|
|
80
|
+
missing = @policy.required_keys.select { |key| env[key].to_s.empty? }
|
|
81
|
+
return if missing.empty?
|
|
82
|
+
|
|
83
|
+
raise EnvironmentSetupError,
|
|
84
|
+
"Missing required fixture environment for deterministic test mode: #{missing.join(", ")}." \
|
|
85
|
+
" Supply the listed keys via runner configuration environment.overrides."
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -10,8 +10,10 @@ module Ace
|
|
|
10
10
|
# Runs tests directly in the current Ruby process without spawning subprocesses
|
|
11
11
|
# This provides significantly faster execution for unit tests that don't need isolation
|
|
12
12
|
class InProcessRunner
|
|
13
|
-
def initialize(timeout: nil)
|
|
13
|
+
def initialize(timeout: nil, launch_env:)
|
|
14
14
|
@timeout = timeout
|
|
15
|
+
@launch_env = launch_env # Hermetic fixture environment applied for the test run
|
|
16
|
+
@saved_env = nil
|
|
15
17
|
end
|
|
16
18
|
|
|
17
19
|
def execute_tests(files, options = {})
|
|
@@ -27,15 +29,16 @@ module Ace
|
|
|
27
29
|
|
|
28
30
|
# Store original verbose setting
|
|
29
31
|
original_verbose = $VERBOSE
|
|
30
|
-
original_mt_no_autorun = ENV["MT_NO_AUTORUN"]
|
|
31
32
|
|
|
32
33
|
begin
|
|
33
34
|
$stdout = stdout_io
|
|
34
35
|
$stderr = stderr_io
|
|
35
36
|
$VERBOSE = nil if options[:suppress_warnings]
|
|
36
37
|
|
|
37
|
-
#
|
|
38
|
-
|
|
38
|
+
# Apply the hermetic fixture environment for the duration of the run:
|
|
39
|
+
# test code reads sanitized, fixture-owned environment state, and any
|
|
40
|
+
# subprocess the tests spawn inherits the same hermetic environment.
|
|
41
|
+
apply_launch_env
|
|
39
42
|
|
|
40
43
|
# Add test directory to load path if not already there
|
|
41
44
|
test_dir = File.expand_path("test")
|
|
@@ -145,12 +148,9 @@ module Ace
|
|
|
145
148
|
$stderr = original_stderr
|
|
146
149
|
$VERBOSE = original_verbose
|
|
147
150
|
|
|
148
|
-
# Restore
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
else
|
|
152
|
-
ENV.delete("MT_NO_AUTORUN")
|
|
153
|
-
end
|
|
151
|
+
# Restore the exact parent environment, including keys removed or
|
|
152
|
+
# added by test code during the run
|
|
153
|
+
restore_launch_env
|
|
154
154
|
end
|
|
155
155
|
|
|
156
156
|
end_time = Time.now
|
|
@@ -195,6 +195,23 @@ module Ace
|
|
|
195
195
|
|
|
196
196
|
private
|
|
197
197
|
|
|
198
|
+
# Swap the process environment to the hermetic fixture environment.
|
|
199
|
+
# Keys absent from the fixture environment (ambient configuration,
|
|
200
|
+
# credentials) are removed for the duration of the run.
|
|
201
|
+
def apply_launch_env
|
|
202
|
+
@saved_env = ENV.to_h
|
|
203
|
+
@launch_env.each { |key, value| ENV[key] = value }
|
|
204
|
+
(@saved_env.keys - @launch_env.keys).each { |key| ENV.delete(key) }
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def restore_launch_env
|
|
208
|
+
return unless @saved_env
|
|
209
|
+
|
|
210
|
+
ENV.clear
|
|
211
|
+
@saved_env.each { |key, value| ENV[key] = value }
|
|
212
|
+
@saved_env = nil
|
|
213
|
+
end
|
|
214
|
+
|
|
198
215
|
def empty_result
|
|
199
216
|
{
|
|
200
217
|
stdout: "",
|
|
@@ -9,9 +9,9 @@ module Ace
|
|
|
9
9
|
module Molecules
|
|
10
10
|
# Intelligently chooses between subprocess and in-process execution based on test type
|
|
11
11
|
class SmartTestExecutor
|
|
12
|
-
def initialize(command_builder: nil, timeout: nil, force_mode: nil)
|
|
13
|
-
@subprocess_executor = TestExecutor.new(command_builder: command_builder, timeout: timeout)
|
|
14
|
-
@in_process_runner = InProcessRunner.new(timeout: timeout)
|
|
12
|
+
def initialize(command_builder: nil, timeout: nil, force_mode: nil, launch_env:)
|
|
13
|
+
@subprocess_executor = TestExecutor.new(command_builder: command_builder, timeout: timeout, launch_env: launch_env)
|
|
14
|
+
@in_process_runner = InProcessRunner.new(timeout: timeout, launch_env: launch_env)
|
|
15
15
|
@test_type_detector = Atoms::TestTypeDetector.new
|
|
16
16
|
@force_mode = force_mode # :subprocess, :direct, or nil for auto
|
|
17
17
|
end
|
|
@@ -9,9 +9,10 @@ module Ace
|
|
|
9
9
|
module Molecules
|
|
10
10
|
# Executes test commands and captures output
|
|
11
11
|
class TestExecutor
|
|
12
|
-
def initialize(command_builder: nil, timeout: nil)
|
|
12
|
+
def initialize(command_builder: nil, timeout: nil, launch_env:)
|
|
13
13
|
@command_builder = command_builder || Atoms::CommandBuilder.new
|
|
14
14
|
@timeout = timeout # In seconds, nil = no timeout
|
|
15
|
+
@launch_env = launch_env # Hermetic fixture environment for every child
|
|
15
16
|
end
|
|
16
17
|
|
|
17
18
|
def execute_tests(files, options = {})
|
|
@@ -32,25 +33,21 @@ module Ace
|
|
|
32
33
|
stderr = ""
|
|
33
34
|
status = nil
|
|
34
35
|
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
"ACE_ASSIGN_FORK_ROOT" => nil
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
# Remove MT_NO_AUTORUN=1 from command if it's there
|
|
36
|
+
# Launch from the hermetic fixture environment: ambient LAB_*, ACE_*,
|
|
37
|
+
# provider and user configuration never reaches the test child.
|
|
38
|
+
# unsetenv_others makes the fixture environment the entire child
|
|
39
|
+
# environment (Process.spawn merges onto the parent by default).
|
|
40
|
+
# MT_NO_AUTORUN is part of the fixture environment.
|
|
41
|
+
env = @launch_env
|
|
45
42
|
command = command.sub(/^MT_NO_AUTORUN=1\s+/, "")
|
|
46
43
|
|
|
47
44
|
begin
|
|
48
45
|
if @timeout
|
|
49
46
|
Timeout.timeout(@timeout) do
|
|
50
|
-
stdout, stderr, status = Open3.capture3(env, command)
|
|
47
|
+
stdout, stderr, status = Open3.capture3(env, command, unsetenv_others: true)
|
|
51
48
|
end
|
|
52
49
|
else
|
|
53
|
-
stdout, stderr, status = Open3.capture3(env, command)
|
|
50
|
+
stdout, stderr, status = Open3.capture3(env, command, unsetenv_others: true)
|
|
54
51
|
end
|
|
55
52
|
rescue Timeout::Error
|
|
56
53
|
stderr = "Test execution timed out after #{@timeout} seconds"
|
|
@@ -51,6 +51,9 @@ module Ace
|
|
|
51
51
|
ensure
|
|
52
52
|
# Restore original directory if we changed it
|
|
53
53
|
Dir.chdir(@original_dir) if @package_dir && Dir.pwd != @original_dir
|
|
54
|
+
|
|
55
|
+
# The fixture environment (and its temp root) belongs to this run only
|
|
56
|
+
@fixture_environment&.cleanup
|
|
54
57
|
end
|
|
55
58
|
|
|
56
59
|
def run_with_package_context
|
|
@@ -63,6 +66,8 @@ module Ace
|
|
|
63
66
|
puts "Running tests in #{File.basename(@package_dir)}..."
|
|
64
67
|
end
|
|
65
68
|
|
|
69
|
+
report_fixture_environment if @configuration.verbose
|
|
70
|
+
|
|
66
71
|
# Check if sequential target execution should be used
|
|
67
72
|
if should_execute_sequentially?
|
|
68
73
|
# Use default "fast" target if no target specified in by-target mode
|
|
@@ -179,6 +184,17 @@ module Ace
|
|
|
179
184
|
@pattern_resolver = Molecules::PatternResolver.new(@configuration)
|
|
180
185
|
@test_detector = Atoms::TestDetector.new(patterns: @configuration.patterns)
|
|
181
186
|
|
|
187
|
+
# Hermetic fixture environment: every test child (subprocess or in-process)
|
|
188
|
+
# is launched from the sanitized allowlisted environment with fixture-owned
|
|
189
|
+
# HOME/XDG directories. Ambient LAB_*/ACE_*/provider/user configuration
|
|
190
|
+
# never reaches deterministic tests. Suite-level fixture configuration is
|
|
191
|
+
# merged under this package's own runner configuration (specific wins).
|
|
192
|
+
@environment_policy = build_environment_policy
|
|
193
|
+
@fixture_environment = Molecules::FixtureEnvironment.new(
|
|
194
|
+
policy: @environment_policy,
|
|
195
|
+
parent_env: ENV.to_h
|
|
196
|
+
).build
|
|
197
|
+
|
|
182
198
|
# Use SmartTestExecutor for intelligent subprocess/direct execution choice
|
|
183
199
|
require_relative "../molecules/smart_test_executor"
|
|
184
200
|
force_mode = if @options[:direct]
|
|
@@ -188,7 +204,8 @@ module Ace
|
|
|
188
204
|
end
|
|
189
205
|
@test_executor = Molecules::SmartTestExecutor.new(
|
|
190
206
|
timeout: @configuration.timeout,
|
|
191
|
-
force_mode: force_mode
|
|
207
|
+
force_mode: force_mode,
|
|
208
|
+
launch_env: @fixture_environment.env
|
|
192
209
|
)
|
|
193
210
|
@result_parser = Atoms::ResultParser.new
|
|
194
211
|
@failure_analyzer = Molecules::FailureAnalyzer.new
|
|
@@ -367,10 +384,29 @@ module Ace
|
|
|
367
384
|
profile: options[:profile],
|
|
368
385
|
execution: config_with_options.execution || {},
|
|
369
386
|
files: options[:files],
|
|
370
|
-
run_in_single_batch: options[:run_in_single_batch]
|
|
387
|
+
run_in_single_batch: options[:run_in_single_batch],
|
|
388
|
+
environment: config_with_options.environment
|
|
371
389
|
)
|
|
372
390
|
end
|
|
373
391
|
|
|
392
|
+
# Verbose diagnostics on stderr: fixture root and override key names only.
|
|
393
|
+
# Override values (potentially credential-like) are never printed.
|
|
394
|
+
def report_fixture_environment
|
|
395
|
+
warn "fixture home: #{@fixture_environment.root}"
|
|
396
|
+
override_names = @environment_policy.overrides.keys.join(", ")
|
|
397
|
+
warn "fixture overrides: #{override_names.empty? ? "none" : override_names}"
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
# Package policy from runner configuration, merged with suite-level
|
|
401
|
+
# fixture configuration propagated by ace-test-suite (specific wins).
|
|
402
|
+
def build_environment_policy
|
|
403
|
+
policy = Models::EnvironmentPolicy.from_config(@configuration.environment)
|
|
404
|
+
channel = ENV[Models::EnvironmentPolicy::SUITE_CHANNEL_KEY]
|
|
405
|
+
return policy unless channel
|
|
406
|
+
|
|
407
|
+
Models::EnvironmentPolicy.from_channel(channel).merge_with(policy)
|
|
408
|
+
end
|
|
409
|
+
|
|
374
410
|
def validate_configuration!
|
|
375
411
|
@configuration.validate!
|
|
376
412
|
rescue ArgumentError => e
|
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
require "rake"
|
|
4
4
|
require "rake/tasklib"
|
|
5
5
|
|
|
6
|
+
# Rakefiles load this file directly in a fresh process; load the runner
|
|
7
|
+
# namespace explicitly (models, molecules, atoms, error classes) instead of
|
|
8
|
+
# relying on another entrypoint having required it first.
|
|
9
|
+
require_relative "../test_runner"
|
|
10
|
+
|
|
6
11
|
module Ace
|
|
7
12
|
module TestRunner
|
|
8
13
|
# Custom Rake task that uses ace-test instead of standard Minitest runner
|
|
@@ -40,17 +45,22 @@ module Ace
|
|
|
40
45
|
# Build ace-test command
|
|
41
46
|
command = build_command
|
|
42
47
|
|
|
43
|
-
# Execute ace-test with sanitized environment
|
|
44
|
-
# Strip assignment context vars to prevent tests from resolving to wrong assignments
|
|
45
48
|
puts command if verbose
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
|
|
50
|
+
# Launch ace-test from a hermetic fixture environment: ambient LAB_*,
|
|
51
|
+
# ACE_*/provider and user configuration never reaches the test run.
|
|
52
|
+
fixture_environment = Molecules::FixtureEnvironment.new(
|
|
53
|
+
policy: Models::EnvironmentPolicy.new,
|
|
54
|
+
parent_env: ENV.to_h
|
|
55
|
+
).build
|
|
56
|
+
# unsetenv_others makes the fixture environment the entire child
|
|
57
|
+
# environment (system merges onto the parent by default).
|
|
58
|
+
success = system(fixture_environment.env, command, unsetenv_others: true)
|
|
51
59
|
|
|
52
60
|
# Exit with proper code for CI/CD
|
|
53
61
|
exit(1) unless success
|
|
62
|
+
ensure
|
|
63
|
+
fixture_environment&.cleanup
|
|
54
64
|
end
|
|
55
65
|
|
|
56
66
|
def build_command
|
|
@@ -28,6 +28,8 @@ module Ace
|
|
|
28
28
|
|
|
29
29
|
def run
|
|
30
30
|
validate_packages!
|
|
31
|
+
puts "Test mode: deterministic (hermetic environment)"
|
|
32
|
+
|
|
31
33
|
test_options = (@config.dig("test_suite", "test_options") || {}).dup
|
|
32
34
|
report_root = normalize_report_root(test_options["report_dir"])
|
|
33
35
|
test_options["report_dir"] = report_root if report_root
|
|
@@ -35,7 +37,8 @@ module Ace
|
|
|
35
37
|
display_manager = create_display_manager
|
|
36
38
|
process_monitor = ProcessMonitor.new(
|
|
37
39
|
@config.dig("test_suite", "max_parallel") || 10,
|
|
38
|
-
package_timeout: @config.dig("test_suite", "timeout")
|
|
40
|
+
package_timeout: @config.dig("test_suite", "timeout"),
|
|
41
|
+
environment_policy: environment_policy
|
|
39
42
|
)
|
|
40
43
|
|
|
41
44
|
# Enrich packages with historical duration data for scheduling
|
|
@@ -89,6 +92,12 @@ module Ace
|
|
|
89
92
|
|
|
90
93
|
private
|
|
91
94
|
|
|
95
|
+
# Hermetic environment policy from suite configuration
|
|
96
|
+
# (test_suite.environment: preserve/overrides/require).
|
|
97
|
+
def environment_policy
|
|
98
|
+
Models::EnvironmentPolicy.from_config(@config.dig("test_suite", "environment"))
|
|
99
|
+
end
|
|
100
|
+
|
|
92
101
|
def validate_packages!
|
|
93
102
|
@packages.each do |package|
|
|
94
103
|
unless Dir.exist?(package["path"])
|
|
@@ -10,10 +10,12 @@ module Ace
|
|
|
10
10
|
class ProcessMonitor
|
|
11
11
|
attr_reader :processes, :max_parallel
|
|
12
12
|
|
|
13
|
-
def initialize(max_parallel = 10, package_timeout: nil, termination_grace_period: 1.0, clock: nil
|
|
13
|
+
def initialize(max_parallel = 10, package_timeout: nil, termination_grace_period: 1.0, clock: nil,
|
|
14
|
+
environment_policy: Models::EnvironmentPolicy.new)
|
|
14
15
|
@max_parallel = max_parallel
|
|
15
16
|
@package_timeout = package_timeout
|
|
16
17
|
@termination_grace_period = termination_grace_period
|
|
18
|
+
@environment_policy = environment_policy
|
|
17
19
|
@clock = clock || -> { Time.now }
|
|
18
20
|
@processes = {}
|
|
19
21
|
@queue = []
|
|
@@ -31,14 +33,22 @@ module Ace
|
|
|
31
33
|
# Build command
|
|
32
34
|
cmd = build_command(package, test_options)
|
|
33
35
|
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
# Launch each package from its own hermetic fixture environment so
|
|
37
|
+
# parallel workers never share (or leak) ambient Lab/ACE/user state.
|
|
38
|
+
# Suite-level fixture configuration propagates to the package worker
|
|
39
|
+
# through the runner-internal channel; the worker merges it under its
|
|
40
|
+
# own runner configuration and the channel never reaches test children.
|
|
41
|
+
fixture_environment = Molecules::FixtureEnvironment.new(
|
|
42
|
+
policy: @environment_policy,
|
|
43
|
+
parent_env: ENV.to_h
|
|
44
|
+
).build
|
|
45
|
+
fixture_environment.env[Models::EnvironmentPolicy::SUITE_CHANNEL_KEY] =
|
|
46
|
+
@environment_policy.channel_payload
|
|
47
|
+
|
|
40
48
|
start_time = now
|
|
41
|
-
stdin, stdout, stderr, thread = Open3.popen3(
|
|
49
|
+
stdin, stdout, stderr, thread = Open3.popen3(
|
|
50
|
+
fixture_environment.env, *cmd, chdir: package["path"], pgroup: true, unsetenv_others: true
|
|
51
|
+
)
|
|
42
52
|
|
|
43
53
|
@processes[package["name"]] = {
|
|
44
54
|
package: package,
|
|
@@ -48,6 +58,7 @@ module Ace
|
|
|
48
58
|
stdin: stdin,
|
|
49
59
|
start_time: start_time,
|
|
50
60
|
callback: callback,
|
|
61
|
+
fixture_environment: fixture_environment,
|
|
51
62
|
output: +"",
|
|
52
63
|
stderr_output: +"",
|
|
53
64
|
report_root: test_options["report_dir"],
|
|
@@ -99,6 +110,7 @@ module Ace
|
|
|
99
110
|
collect_remaining_output(process_info)
|
|
100
111
|
results = build_results(process_info, elapsed, exit_status)
|
|
101
112
|
close_streams(process_info)
|
|
113
|
+
process_info[:fixture_environment]&.cleanup
|
|
102
114
|
|
|
103
115
|
# Final callback
|
|
104
116
|
if callback
|
|
@@ -164,6 +176,7 @@ module Ace
|
|
|
164
176
|
end
|
|
165
177
|
|
|
166
178
|
close_streams(process_info)
|
|
179
|
+
process_info[:fixture_environment]&.cleanup
|
|
167
180
|
end
|
|
168
181
|
|
|
169
182
|
@processes.clear
|
|
@@ -180,7 +193,15 @@ module Ace
|
|
|
180
193
|
private
|
|
181
194
|
|
|
182
195
|
def build_command(package, options)
|
|
183
|
-
|
|
196
|
+
# Run the child with this suite's own Ruby binary and a bundler
|
|
197
|
+
# bootstrap: the hermetic fixture env blocks RUBYOPT/BUNDLE_/GEM_ and
|
|
198
|
+
# redirects HOME/XDG, so a PATH-resolved interpreter can fail through
|
|
199
|
+
# manager shims and children have no dependency surface beyond the
|
|
200
|
+
# Ruby install's default gem dir. -rbundler/setup makes bundler
|
|
201
|
+
# discover the checkout Gemfile by walking up from the package cwd,
|
|
202
|
+
# giving every child the same source-of-truth dependency set without
|
|
203
|
+
# ambient env or globally installed gems.
|
|
204
|
+
cmd_parts = [RbConfig.ruby, "-rbundler/setup", ace_test_executable]
|
|
184
205
|
|
|
185
206
|
# Suite package execution intentionally bypasses grouped mode so each
|
|
186
207
|
# package runs its full target scope as one batch under suite orchestration.
|
|
@@ -207,6 +228,10 @@ module Ace
|
|
|
207
228
|
cmd_parts
|
|
208
229
|
end
|
|
209
230
|
|
|
231
|
+
def ace_test_executable
|
|
232
|
+
File.expand_path("../../../../exe/ace-test", __dir__)
|
|
233
|
+
end
|
|
234
|
+
|
|
210
235
|
def now
|
|
211
236
|
@clock.call
|
|
212
237
|
end
|
data/lib/ace/test_runner.rb
CHANGED
|
@@ -15,6 +15,12 @@ require "time"
|
|
|
15
15
|
|
|
16
16
|
require_relative "test_runner/version"
|
|
17
17
|
|
|
18
|
+
module Ace
|
|
19
|
+
module TestRunner
|
|
20
|
+
class Error < StandardError; end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
18
24
|
# CLI and commands
|
|
19
25
|
require_relative "test_runner/cli"
|
|
20
26
|
|
|
@@ -23,6 +29,7 @@ require_relative "test_runner/models/test_result"
|
|
|
23
29
|
require_relative "test_runner/models/test_failure"
|
|
24
30
|
require_relative "test_runner/models/test_configuration"
|
|
25
31
|
require_relative "test_runner/models/test_report"
|
|
32
|
+
require_relative "test_runner/models/environment_policy"
|
|
26
33
|
|
|
27
34
|
# Atoms - Basic utilities (always needed)
|
|
28
35
|
require_relative "test_runner/atoms/test_detector"
|
|
@@ -32,8 +39,10 @@ require_relative "test_runner/atoms/timestamp_generator"
|
|
|
32
39
|
require_relative "test_runner/atoms/lazy_loader"
|
|
33
40
|
require_relative "test_runner/atoms/report_path_resolver"
|
|
34
41
|
require_relative "test_runner/atoms/report_directory_resolver"
|
|
42
|
+
require_relative "test_runner/atoms/environment_sanitizer"
|
|
35
43
|
|
|
36
44
|
# Molecules - Core operations (always needed for basic test running)
|
|
45
|
+
require_relative "test_runner/molecules/fixture_environment"
|
|
37
46
|
require_relative "test_runner/molecules/test_executor"
|
|
38
47
|
require_relative "test_runner/molecules/failure_analyzer"
|
|
39
48
|
require_relative "test_runner/molecules/report_storage"
|
|
@@ -54,8 +63,6 @@ require_relative "test_runner/organisms/report_generator"
|
|
|
54
63
|
|
|
55
64
|
module Ace
|
|
56
65
|
module TestRunner
|
|
57
|
-
class Error < StandardError; end
|
|
58
|
-
|
|
59
66
|
# Define module namespaces
|
|
60
67
|
module Commands; end
|
|
61
68
|
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ace-test-runner
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.27.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Michal Czyz
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-09-
|
|
10
|
+
date: 2026-09-29 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: ace-support-cli
|
|
@@ -171,6 +171,7 @@ files:
|
|
|
171
171
|
- exe/ace-test-suite
|
|
172
172
|
- lib/ace/test_runner.rb
|
|
173
173
|
- lib/ace/test_runner/atoms/command_builder.rb
|
|
174
|
+
- lib/ace/test_runner/atoms/environment_sanitizer.rb
|
|
174
175
|
- lib/ace/test_runner/atoms/lazy_loader.rb
|
|
175
176
|
- lib/ace/test_runner/atoms/line_number_resolver.rb
|
|
176
177
|
- lib/ace/test_runner/atoms/report_directory_resolver.rb
|
|
@@ -187,6 +188,7 @@ files:
|
|
|
187
188
|
- lib/ace/test_runner/formatters/markdown_formatter.rb
|
|
188
189
|
- lib/ace/test_runner/formatters/progress_file_formatter.rb
|
|
189
190
|
- lib/ace/test_runner/formatters/progress_formatter.rb
|
|
191
|
+
- lib/ace/test_runner/models/environment_policy.rb
|
|
190
192
|
- lib/ace/test_runner/models/test_configuration.rb
|
|
191
193
|
- lib/ace/test_runner/models/test_failure.rb
|
|
192
194
|
- lib/ace/test_runner/models/test_report.rb
|
|
@@ -197,6 +199,7 @@ files:
|
|
|
197
199
|
- lib/ace/test_runner/molecules/deprecation_fixer.rb
|
|
198
200
|
- lib/ace/test_runner/molecules/failed_package_reporter.rb
|
|
199
201
|
- lib/ace/test_runner/molecules/failure_analyzer.rb
|
|
202
|
+
- lib/ace/test_runner/molecules/fixture_environment.rb
|
|
200
203
|
- lib/ace/test_runner/molecules/in_process_runner.rb
|
|
201
204
|
- lib/ace/test_runner/molecules/package_resolver.rb
|
|
202
205
|
- lib/ace/test_runner/molecules/pattern_resolver.rb
|