sus 0.37.2 → 0.38.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 356b7bfa38432ef766bae5c9ef40f7e0d08cacf9423544c1226ac86c92e78f16
4
- data.tar.gz: 21ea8115de9084304ad615db7d33d7facafa7255e1d3bafd408c9885ce14044e
3
+ metadata.gz: d3dcb5de6597ebcc7d81b78022f79f4b5de62ec5c902f76bed8f23d2fe9ca8fa
4
+ data.tar.gz: 1eeddde2ea281d7e0d277726905c233de4febb80f5560f03b48b36d6e1d9e0e6
5
5
  SHA512:
6
- metadata.gz: a3db361e81379abc117c1f977a979b66f6dcd1c42270fee895db1cb1d1bef0f6c64ed45fe51084de4d0e4671d6a83475cc7dc74523221340dbfeee5921bdbc8f
7
- data.tar.gz: fe44089f8600b62d4255ecdaeb3b8e15060735a7ad8e5e9d78a8e5a5dfb05a63ae999b0681ccba55ce18048fa9cd5c94c8cbb221561b72328eee96b273d002f1
6
+ metadata.gz: 542588a3f55fa3d835ccced647fea9b5469f01c82c79ea2bf5fbaf96f45faf6f98e621a8c1802934c7cc2a56ab763ab4464346606449165fc3321325fb378dee
7
+ data.tar.gz: 2de2a0703afeb06798257b642d507cfee6a2377b6fd23ea391db10bede8b9b093e9f7e9c02a6bdbd2069da0318c089aaea5b16b3dc70033a537f86e6e3268f9f
checksums.yaml.gz.sig CHANGED
Binary file
@@ -198,6 +198,50 @@ end
198
198
 
199
199
  Note the use of `unique: adapter.name` to ensure each test is uniquely identified, which is useful for reporting and debugging - otherwise the same test line number would be used for all iterations, which can make it hard to identify which specific test failed.
200
200
 
201
+ ## Isolated Ruby
202
+
203
+ Use `Sus::Fixtures::IsolatedRubyContext` to evaluate Ruby in a fresh process and assert on its result. This is useful for code that relies on a working directory, environment variables, or constants which must be isolated from other tests.
204
+
205
+ ```ruby
206
+ require "sus/fixtures/isolated_ruby_context"
207
+ require "sus/fixtures/temporary_directory_context"
208
+
209
+ describe "isolated evaluation" do
210
+ include Sus::Fixtures::IsolatedRubyContext
211
+ include Sus::Fixtures::TemporaryDirectoryContext
212
+
213
+ it "reads files in the fixture directory" do
214
+ File.write(File.join(root, "value.txt"), "example")
215
+ result = isolated_ruby(<<~RUBY, chdir: root)
216
+ {value: File.read("value.txt")}
217
+ RUBY
218
+
219
+ expect(result[:value]).to be == "example"
220
+ end
221
+ end
222
+ ```
223
+
224
+ The final expression is returned using `Marshal.dump` and `Marshal.load`, preserving Ruby types, hash keys, string encodings, and shared or cyclic references. The result must support Marshal serialization, and any custom classes it uses must also be loaded in the caller.
225
+
226
+ Exceptions raised while loading requested features, evaluating source, or serializing the result are marshaled back and re-raised in the caller with their original class, message, and backtrace. Custom exception classes must also be loaded in the caller. Returning an exception object as the final expression returns it as a value.
227
+
228
+ Printed output goes to the inherited stderr, keeping it separate from the result. An unsuccessful child that cannot return an exception raises `IsolatedRubyContext::Error`, which exposes its `status`; diagnostics appear directly on stderr. This includes startup failures, unsuccessful explicit exits, and exceptions that cannot be marshaled. A successful exit without a result, such as `exit(0)`, returns `nil`.
229
+
230
+ The fixture uses the current Ruby interpreter and defaults to the caller's working directory. `chdir:` changes only the child's directory, so evaluations can run concurrently. The child inherits the environment, including `RUBYOPT` so coverage and other startup hooks continue to run. `env:` supplies child environment overrides; a nil value removes a variable. For a clean startup without inherited Ruby or Bundler hooks, pass `env: {"RUBYOPT" => nil, "BUNDLER_SETUP" => nil}`.
231
+
232
+ Use `requires:` to load features before evaluating the source. To set up a particular bundle, use an absolute Gemfile path:
233
+
234
+ ```ruby
235
+ result = isolated_ruby(
236
+ 'require "my_gem"; {version: MyGem::VERSION}',
237
+ chdir: root,
238
+ env: {"BUNDLE_GEMFILE" => File.expand_path("gems.rb")},
239
+ requires: ["bundler/setup"]
240
+ )
241
+ ```
242
+
243
+ The fixture accepts source code rather than a block; parent local variables and loaded Ruby state are not transferred to the child. It works independently of `TemporaryDirectoryContext`.
244
+
201
245
  ## Best Practices
202
246
 
203
247
  1. **Organize by domain**: Group related shared contexts together in modules
data/lib/sus/config.rb CHANGED
@@ -27,8 +27,9 @@ module Sus
27
27
  # Load configuration from the given root directory.
28
28
  # @parameter root [String] The root directory to load configuration from.
29
29
  # @parameter arguments [Array] Command line arguments to parse.
30
+ # @parameter env [Hash] The environment to inspect for debug/verbose settings.
30
31
  # @returns [Config] A new Config instance.
31
- def self.load(root: Dir.pwd, arguments: ARGV)
32
+ def self.load(root: Dir.pwd, arguments: ARGV, env: ENV)
32
33
  derived = Class.new(self)
33
34
 
34
35
  if path = self.path(root)
@@ -38,12 +39,40 @@ module Sus
38
39
  end
39
40
 
40
41
  options = {
41
- verbose: !!arguments.delete("--verbose")
42
+ verbose: !!arguments.delete("--verbose") || self.verbose_from_environment?(env)
42
43
  }
43
44
 
44
45
  return derived.new(root, arguments, **options)
45
46
  end
46
47
 
48
+ # Maps CI environment variables to the values they take when the CI provider is running in debug/verbose mode. When any of these match, we enable verbose output automatically.
49
+ #
50
+ # - `RUNNER_DEBUG` is set by GitHub Actions when a workflow is re-run with "Enable debug logging".
51
+ # - `CI_DEBUG_TRACE` is set by GitLab CI when debug logging (tracing) is enabled.
52
+ # - `BUILDKITE_AGENT_DEBUG` is set by Buildkite when agent debug is enabled.
53
+ # - `SYSTEM_DEBUG` is set by Azure Pipelines when the `system.debug` variable is enabled.
54
+ # - `SUS_VERBOSE` can be set explicitly to enable verbose output regardless of the CI provider.
55
+ DEBUG_ENVIRONMENT = {
56
+ "SUS_VERBOSE" => "true",
57
+ "RUNNER_DEBUG" => "1",
58
+ "CI_DEBUG_TRACE" => "true",
59
+ "BUILDKITE_AGENT_DEBUG" => "true",
60
+ "SYSTEM_DEBUG" => "true",
61
+ }
62
+
63
+ # Whether verbose output should be enabled based on the environment.
64
+ #
65
+ # Detects CI environments that request debug logging, e.g. GitHub Actions
66
+ # sets `RUNNER_DEBUG=1` when a workflow is re-run with "Enable debug logging".
67
+ #
68
+ # @parameter env [Hash] The environment to inspect.
69
+ # @returns [Boolean] Whether verbose output should be enabled.
70
+ def self.verbose_from_environment?(env = ENV)
71
+ DEBUG_ENVIRONMENT.any? do |key, value|
72
+ env[key] == value
73
+ end
74
+ end
75
+
47
76
  # Initialize a new Config instance.
48
77
  # @parameter root [String] The root directory for the project.
49
78
  # @parameter paths [Array] Optional paths to specific test files.
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2026, by Samuel Williams.
5
+
6
+ require "rbconfig"
7
+
8
+ # @namespace
9
+ module Sus
10
+ # @namespace
11
+ module Fixtures
12
+ # Evaluates Ruby in a fresh process and returns its result through Marshal.
13
+ module IsolatedRubyContext
14
+ # Raised when the child process exits unsuccessfully without returning an exception.
15
+ class Error < RuntimeError
16
+ # @parameter status [Process::Status] The child process's exit status.
17
+ def initialize(status)
18
+ @status = status
19
+ super("Isolated Ruby failed (#{status})")
20
+ end
21
+
22
+ # @attribute [Process::Status] The child process's exit status.
23
+ attr :status
24
+ end
25
+
26
+ # Evaluate source using the current Ruby interpreter, without sharing Ruby state or changing the caller's working directory.
27
+ # The final expression is serialized with Marshal.dump and restored with Marshal.load. Printed output goes to the inherited stderr.
28
+ # Exceptions are marshaled back and re-raised with their original backtraces. SystemExit follows the child process's exit status.
29
+ # @parameter source [String] Ruby source code to evaluate.
30
+ # @parameter chdir [String] The child process's working directory.
31
+ # @parameter env [Hash(String, String | Nil)] Child environment overrides; nil removes a variable. The environment is inherited by default, including RUBYOPT for coverage hooks.
32
+ # @parameter requires [Array(String)] Features to require before evaluating source, such as bundler/setup.
33
+ # @returns [Object] The unmarshaled result, or nil if the child exits successfully without a result. Classes used by the result must be available in the caller.
34
+ # @raises [Exception] The exception raised in the child process, if it can be marshaled back.
35
+ # @raises [Error] If the child exits unsuccessfully without returning an exception.
36
+ # @raises [ArgumentError] If the result or exception uses a class unavailable in the caller.
37
+ def isolated_ruby(source, chdir: Dir.pwd, env: {}, requires: [])
38
+ script = <<~'RUBY'
39
+ ->(output) do
40
+ $stdout.reopen($stderr)
41
+ begin
42
+ source = $stdin.read
43
+ ARGV.each{|feature| require feature}
44
+ result = eval(source, TOPLEVEL_BINDING, File.join(Dir.pwd, "(isolated ruby)"))
45
+ output.write(Marshal.dump([result, nil]))
46
+ rescue SystemExit
47
+ raise
48
+ rescue Exception => error
49
+ output.write(Marshal.dump([nil, error]))
50
+ end
51
+ end.call($stdout.dup.binmode)
52
+ RUBY
53
+
54
+ output = IO.popen([env, RbConfig.ruby, "-e", script, "--", *requires], "r+b", chdir: chdir) do |process|
55
+ begin
56
+ process.write(source)
57
+ rescue Errno::EPIPE
58
+ # A startup failure may close stdin before accepting the source:
59
+ end
60
+ process.close_write
61
+ process.read
62
+ end
63
+ status = $?
64
+ raise Error.new(status) unless status.success?
65
+ return nil if output.empty?
66
+
67
+ result, error = Marshal.load(output)
68
+ raise error if error
69
+ result
70
+ end
71
+ end
72
+ end
73
+ end
data/lib/sus/version.rb CHANGED
@@ -5,5 +5,5 @@
5
5
 
6
6
  # @namespace
7
7
  module Sus
8
- VERSION = "0.37.2"
8
+ VERSION = "0.38.0"
9
9
  end
data/readme.md CHANGED
@@ -33,6 +33,10 @@ Please see the [project documentation](https://socketry.github.io/sus/) for more
33
33
 
34
34
  Please see the [project releases](https://socketry.github.io/sus/releases/index) for all releases.
35
35
 
36
+ ### v0.38.0
37
+
38
+ - Add `Sus::Fixtures::IsolatedRubyContext#isolated_ruby` for evaluating Ruby in a fresh process with optional working directory and environment overrides, returning Ruby values and re-raising exceptions in the caller.
39
+
36
40
  ### v0.37.2
37
41
 
38
42
  - Make `Sus::Fixtures::TemporaryDirectoryContext` ignore temporary directory cleanup failures.
data/releases.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Releases
2
2
 
3
+ ## v0.38.0
4
+
5
+ - Add `Sus::Fixtures::IsolatedRubyContext#isolated_ruby` for evaluating Ruby in a fresh process with optional working directory and environment overrides, returning Ruby values and re-raising exceptions in the caller.
6
+
3
7
  ## v0.37.2
4
8
 
5
9
  - Make `Sus::Fixtures::TemporaryDirectoryContext` ignore temporary directory cleanup failures.
data.tar.gz.sig CHANGED
@@ -1,2 +1,2 @@
1
- F�a���@2R�)Y�R���g쮉Z�^�&,j(t��]��zN���upd��!`���,_��]�ܸ����O�,����nr�� ��Dc�����cg�L�[ Ve��"%(駗���P,\����@�����Po�Z)��&��dM ��W���
2
- 7�׃`Ơ����ۨ�cl���lӵZHvV:��Hel���sM"CimO�������X� ������r;��N����<�����"�`�3��˵��y����eԭlH���o�������ۭzq�� �,�S�,��6��&U�~tH9�>bլ�ə��m �:��p���� N2h�9��x� �R��YX1�]��J�`��!y�<�;t�T3�>�׋�!����U
1
+ K�%��ZP�7�$<��Q��x��|��O�#�X����bD�`u�`��@�s���92+K x���O��B���h����x��eO�g���n��6�����p��ʈ2Q��@�.�L#��MU/�H�?О)G3as��ZQ��*d؊ ��!�5P���Gv|L�!��tZ��m%HP:;�Q4Wd DW!�����],�ܶ��@�#� Clt=I�ձ�*�R45�Z�Q���[d��M�y0��];���!��F���#` -E#�5�\N�-O�I{ء�
2
+ ������4���ё`���FK�D>���n:���E���FdPV^^R�^BKܐ{ʾ�L��nD4�„�s8�o� ����!��|
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.37.2
4
+ version: 0.38.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -70,6 +70,7 @@ files:
70
70
  - lib/sus/file.rb
71
71
  - lib/sus/filter.rb
72
72
  - lib/sus/fixtures.rb
73
+ - lib/sus/fixtures/isolated_ruby_context.rb
73
74
  - lib/sus/fixtures/temporary_directory_context.rb
74
75
  - lib/sus/have.rb
75
76
  - lib/sus/have/all.rb
@@ -127,7 +128,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
127
128
  - !ruby/object:Gem::Version
128
129
  version: '0'
129
130
  requirements: []
130
- rubygems_version: 4.0.10
131
+ rubygems_version: 4.0.16
131
132
  specification_version: 4
132
133
  summary: A fast and scalable test runner.
133
134
  test_files: []
metadata.gz.sig CHANGED
Binary file