serialbench 0.1.1 → 0.1.3

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.
Files changed (100) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/benchmark.yml +273 -220
  3. data/.github/workflows/rake.yml +26 -0
  4. data/.github/workflows/windows-debug.yml +171 -0
  5. data/.gitignore +32 -0
  6. data/.rubocop.yml +1 -0
  7. data/.rubocop_todo.yml +274 -0
  8. data/Gemfile +14 -1
  9. data/README.adoc +292 -1118
  10. data/Rakefile +0 -55
  11. data/config/benchmarks/full.yml +29 -0
  12. data/config/benchmarks/short.yml +26 -0
  13. data/config/environments/asdf-ruby-3.2.yml +8 -0
  14. data/config/environments/asdf-ruby-3.3.yml +8 -0
  15. data/config/environments/docker-ruby-3.0.yml +9 -0
  16. data/config/environments/docker-ruby-3.1.yml +9 -0
  17. data/config/environments/docker-ruby-3.2.yml +9 -0
  18. data/config/environments/docker-ruby-3.3.yml +9 -0
  19. data/config/environments/docker-ruby-3.4.yml +9 -0
  20. data/data/schemas/result.yml +29 -0
  21. data/docker/Dockerfile.alpine +33 -0
  22. data/docker/{Dockerfile.benchmark → Dockerfile.ubuntu} +4 -3
  23. data/docker/README.md +2 -2
  24. data/docs/PLATFORM_VALIDATION_FIX.md +79 -0
  25. data/docs/SYCK_YAML_FIX.md +91 -0
  26. data/docs/WEBSITE_COMPLETION_PLAN.md +440 -0
  27. data/docs/WINDOWS_LIBXML_FIX.md +136 -0
  28. data/docs/WINDOWS_SETUP.md +122 -0
  29. data/exe/serialbench +1 -1
  30. data/lib/serialbench/benchmark_runner.rb +261 -423
  31. data/lib/serialbench/cli/base_cli.rb +51 -0
  32. data/lib/serialbench/cli/benchmark_cli.rb +453 -0
  33. data/lib/serialbench/cli/environment_cli.rb +181 -0
  34. data/lib/serialbench/cli/resultset_cli.rb +261 -0
  35. data/lib/serialbench/cli/ruby_build_cli.rb +225 -0
  36. data/lib/serialbench/cli/validate_cli.rb +88 -0
  37. data/lib/serialbench/cli.rb +61 -600
  38. data/lib/serialbench/config_manager.rb +129 -0
  39. data/lib/serialbench/models/benchmark_config.rb +75 -0
  40. data/lib/serialbench/models/benchmark_result.rb +81 -0
  41. data/lib/serialbench/models/environment_config.rb +72 -0
  42. data/lib/serialbench/models/platform.rb +111 -0
  43. data/lib/serialbench/models/result.rb +80 -0
  44. data/lib/serialbench/models/result_set.rb +79 -0
  45. data/lib/serialbench/models/result_store.rb +108 -0
  46. data/lib/serialbench/models.rb +54 -0
  47. data/lib/serialbench/ruby_build_manager.rb +149 -0
  48. data/lib/serialbench/runners/asdf_runner.rb +296 -0
  49. data/lib/serialbench/runners/base.rb +32 -0
  50. data/lib/serialbench/runners/docker_runner.rb +140 -0
  51. data/lib/serialbench/runners/local_runner.rb +71 -0
  52. data/lib/serialbench/serializers/base_serializer.rb +9 -17
  53. data/lib/serialbench/serializers/json/base_json_serializer.rb +4 -4
  54. data/lib/serialbench/serializers/json/json_serializer.rb +0 -2
  55. data/lib/serialbench/serializers/json/oj_serializer.rb +0 -2
  56. data/lib/serialbench/serializers/json/rapidjson_serializer.rb +1 -1
  57. data/lib/serialbench/serializers/json/yajl_serializer.rb +0 -2
  58. data/lib/serialbench/serializers/toml/base_toml_serializer.rb +5 -5
  59. data/lib/serialbench/serializers/toml/toml_rb_serializer.rb +1 -3
  60. data/lib/serialbench/serializers/toml/tomlib_serializer.rb +1 -3
  61. data/lib/serialbench/serializers/toml/tomlrb_serializer.rb +56 -0
  62. data/lib/serialbench/serializers/xml/base_xml_serializer.rb +4 -9
  63. data/lib/serialbench/serializers/xml/libxml_serializer.rb +4 -10
  64. data/lib/serialbench/serializers/xml/nokogiri_serializer.rb +2 -4
  65. data/lib/serialbench/serializers/xml/oga_serializer.rb +4 -10
  66. data/lib/serialbench/serializers/xml/ox_serializer.rb +2 -4
  67. data/lib/serialbench/serializers/xml/rexml_serializer.rb +3 -5
  68. data/lib/serialbench/serializers/yaml/base_yaml_serializer.rb +5 -1
  69. data/lib/serialbench/serializers/yaml/psych_serializer.rb +1 -1
  70. data/lib/serialbench/serializers/yaml/syck_serializer.rb +60 -23
  71. data/lib/serialbench/serializers.rb +23 -6
  72. data/lib/serialbench/site_generator.rb +283 -0
  73. data/lib/serialbench/templates/assets/css/benchmark_report.css +535 -0
  74. data/lib/serialbench/templates/assets/css/format_based.css +474 -0
  75. data/lib/serialbench/templates/assets/css/themes.css +589 -0
  76. data/lib/serialbench/templates/assets/js/chart_helpers.js +411 -0
  77. data/lib/serialbench/templates/assets/js/dashboard.js +795 -0
  78. data/lib/serialbench/templates/assets/js/navigation.js +142 -0
  79. data/lib/serialbench/templates/base.liquid +49 -0
  80. data/lib/serialbench/templates/format_based.liquid +507 -0
  81. data/lib/serialbench/templates/partials/chart_section.liquid +4 -0
  82. data/lib/serialbench/version.rb +1 -1
  83. data/lib/serialbench/yaml_validator.rb +36 -0
  84. data/lib/serialbench.rb +2 -31
  85. data/serialbench.gemspec +15 -3
  86. metadata +106 -25
  87. data/.github/workflows/ci.yml +0 -74
  88. data/.github/workflows/docker.yml +0 -246
  89. data/config/ci.yml +0 -22
  90. data/config/full.yml +0 -30
  91. data/docker/run-benchmarks.sh +0 -356
  92. data/lib/serialbench/chart_generator.rb +0 -821
  93. data/lib/serialbench/result_formatter.rb +0 -182
  94. data/lib/serialbench/result_merger.rb +0 -1201
  95. data/lib/serialbench/serializers/xml/base_parser.rb +0 -69
  96. data/lib/serialbench/serializers/xml/libxml_parser.rb +0 -98
  97. data/lib/serialbench/serializers/xml/nokogiri_parser.rb +0 -111
  98. data/lib/serialbench/serializers/xml/oga_parser.rb +0 -85
  99. data/lib/serialbench/serializers/xml/ox_parser.rb +0 -64
  100. data/lib/serialbench/serializers/xml/rexml_parser.rb +0 -129
data/Rakefile CHANGED
@@ -5,59 +5,4 @@ require 'rspec/core/rake_task'
5
5
 
6
6
  RSpec::Core::RakeTask.new(:spec)
7
7
 
8
- desc 'Run benchmarks'
9
- task :benchmark do
10
- require_relative 'lib/serialbench'
11
-
12
- puts 'Running XML benchmarks...'
13
- results = Serialbench.run_benchmarks
14
-
15
- puts "\nGenerating reports..."
16
- report_files = Serialbench.generate_reports(results)
17
-
18
- puts "\nBenchmark complete!"
19
- puts 'Reports generated:'
20
- puts " HTML: #{report_files[:html]}"
21
- puts " AsciiDoc: #{report_files[:asciidoc]}"
22
- puts " Charts: #{report_files[:charts].length} SVG files"
23
- end
24
-
25
- desc 'Install all XML library dependencies'
26
- task :install_deps do
27
- gems = %w[ox nokogiri libxml-ruby oga memory_profiler]
28
-
29
- puts 'Installing XML library dependencies...'
30
- gems.each do |gem_name|
31
- puts "Installing #{gem_name}..."
32
- system("gem install #{gem_name}")
33
- rescue StandardError => e
34
- puts "Warning: Failed to install #{gem_name}: #{e.message}"
35
- end
36
- puts 'Done!'
37
- end
38
-
39
- desc 'Check which XML libraries are available'
40
- task :check_libs do
41
- require_relative 'lib/serialbench'
42
-
43
- runner = Serialbench::BenchmarkRunner.new
44
-
45
- puts 'XML Library Availability Check'
46
- puts '=' * 40
47
-
48
- runner.parsers.each do |parser|
49
- status = parser.available? ? '✓ Available' : '✗ Not available'
50
- version = parser.available? ? " (#{parser.version})" : ''
51
- puts "#{parser.name.ljust(15)} #{status}#{version}"
52
- end
53
-
54
- puts "\nMemory profiler: #{Serialbench::MemoryProfiler.available? ? '✓ Available' : '✗ Not available'}"
55
- end
56
-
57
- desc 'Clean generated files'
58
- task :clean do
59
- FileUtils.rm_rf('results')
60
- puts 'Cleaned generated files'
61
- end
62
-
63
8
  task default: :spec
@@ -0,0 +1,29 @@
1
+ # Configuration for comprehensive benchmarks - Full testing with all data sizes
2
+ # Used by Docker script for complete performance analysis
3
+
4
+ # Run name for identification
5
+ name: full-benchmark
6
+
7
+ data_sizes:
8
+ - small
9
+ - medium
10
+ - large
11
+
12
+ formats:
13
+ - xml
14
+ - json
15
+ - yaml
16
+ - toml
17
+
18
+ iterations:
19
+ small: 20
20
+ medium: 5
21
+ large: 2
22
+
23
+ operations:
24
+ - parse
25
+ - generate
26
+ - streaming
27
+ - memory
28
+
29
+ warmup: 3
@@ -0,0 +1,26 @@
1
+ # Configuration for short benchmarks - Minimal memory usage for CI runners
2
+ # Designed for Windows and Ubuntu runners with limited memory
3
+
4
+ # Run name for identification
5
+ name: short-benchmark
6
+
7
+ data_sizes:
8
+ - small
9
+
10
+ formats:
11
+ - xml
12
+ - json
13
+ - yaml
14
+ - toml
15
+
16
+ iterations:
17
+ small: 5
18
+ medium: 2
19
+ large: 1
20
+
21
+ operations:
22
+ - parse
23
+ - generate
24
+ - streaming
25
+
26
+ warmup: 2
@@ -0,0 +1,8 @@
1
+ ---
2
+ name: ruby-324-asdf
3
+ kind: asdf
4
+ created_at: '2025-06-12T22:54:43+08:00'
5
+ ruby_build_tag: 3.2.4
6
+ description: ASDF environment
7
+ asdf:
8
+ auto_install: true
@@ -0,0 +1,8 @@
1
+ ---
2
+ name: ruby-332-asdf
3
+ kind: asdf
4
+ created_at: '2025-06-12T22:53:24+08:00'
5
+ ruby_build_tag: 3.3.2
6
+ description: ASDF environment
7
+ asdf:
8
+ auto_install: true
@@ -0,0 +1,9 @@
1
+ ---
2
+ name: docker-ruby-3.0
3
+ kind: docker
4
+ created_at: '2025-06-13T15:18:43+08:00'
5
+ ruby_build_tag: "3.0.7"
6
+ description: Docker environment for Ruby 3.0 benchmarks
7
+ docker:
8
+ image: 'ruby:3.0-slim'
9
+ dockerfile: '../../docker/Dockerfile.ubuntu'
@@ -0,0 +1,9 @@
1
+ ---
2
+ name: docker-ruby-3.1
3
+ kind: docker
4
+ created_at: '2025-06-13T15:18:43+08:00'
5
+ ruby_build_tag: "3.1.6"
6
+ description: Docker environment for Ruby 3.1 benchmarks
7
+ docker:
8
+ image: 'ruby:3.1-slim'
9
+ dockerfile: '../../docker/Dockerfile.ubuntu'
@@ -0,0 +1,9 @@
1
+ ---
2
+ name: docker-ruby-3.2
3
+ kind: docker
4
+ created_at: '2025-06-13T15:18:43+08:00'
5
+ ruby_build_tag: "3.2.4"
6
+ description: Docker environment for Ruby 3.2 benchmarks
7
+ docker:
8
+ image: 'ruby:3.2-slim'
9
+ dockerfile: '../../docker/Dockerfile.ubuntu'
@@ -0,0 +1,9 @@
1
+ ---
2
+ name: docker-ruby-3.3
3
+ kind: docker
4
+ created_at: '2025-06-13T15:18:43+08:00'
5
+ ruby_build_tag: "3.3.7"
6
+ description: Docker environment for Ruby 3.3 benchmarks
7
+ docker:
8
+ image: 'ruby:3.3-slim'
9
+ dockerfile: '../../docker/Dockerfile.ubuntu'
@@ -0,0 +1,9 @@
1
+ ---
2
+ name: docker-ruby-3.4
3
+ kind: docker
4
+ created_at: '2025-06-13T15:18:43+08:00'
5
+ ruby_build_tag: "3.4.1"
6
+ description: Docker environment for Ruby 3.4 benchmarks
7
+ docker:
8
+ image: 'ruby:3.4-slim'
9
+ dockerfile: '../../docker/Dockerfile.ubuntu'
@@ -0,0 +1,29 @@
1
+ ---
2
+ type: object
3
+ title: Serialbench Result
4
+ description: Schema for serialbench benchmark result files (basic structure validation)
5
+ properties:
6
+ platform:
7
+ type: object
8
+ properties:
9
+ platform_string:
10
+ type: string
11
+ os:
12
+ type: string
13
+ arch:
14
+ type: string
15
+ ruby_version:
16
+ type: string
17
+ metadata:
18
+ type: object
19
+ environment_config:
20
+ type: object
21
+ properties:
22
+ name:
23
+ type: string
24
+ kind:
25
+ type: string
26
+ benchmark_config:
27
+ type: object
28
+ benchmark_result:
29
+ type: object
@@ -0,0 +1,33 @@
1
+ # Alpine-based Ruby benchmark environment using official Ruby Alpine images
2
+ ARG BASE_IMAGE=ruby:3.4-alpine
3
+ FROM ${BASE_IMAGE}
4
+
5
+ # Install system dependencies for XML libraries and build tools
6
+ RUN apk add --no-cache \
7
+ build-base \
8
+ libxml2-dev \
9
+ libxslt-dev \
10
+ yaml-dev \
11
+ zlib-dev \
12
+ openssl-dev \
13
+ linux-headers \
14
+ git
15
+
16
+ # Set working directory
17
+ WORKDIR /app
18
+
19
+ # Copy the entire application (gemspec needs full context)
20
+ COPY . .
21
+
22
+ # Update bundler and configure bundle for cross-platform compatibility
23
+ RUN gem install bundler:2.5.22 && \
24
+ bundle config set --local deployment 'false' && \
25
+ bundle config set --local path '/usr/local/bundle' && \
26
+ bundle config set --local force_ruby_platform true && \
27
+ bundle install --jobs 4 --retry 3
28
+
29
+ # Create results directory
30
+ RUN mkdir -p /app/results
31
+
32
+ # Default command runs benchmarks
33
+ ENTRYPOINT ["bundle", "exec", "serialbench"]
@@ -1,14 +1,15 @@
1
1
  # Multi-stage Dockerfile for Ruby serialization benchmarks
2
2
  # Supports multiple Ruby versions for comprehensive testing
3
3
 
4
- ARG RUBY_VERSION=3.3
5
- FROM ruby:${RUBY_VERSION}
4
+ ARG BASE_IMAGE=ruby:3.4-slim
5
+ FROM ${BASE_IMAGE}
6
6
 
7
7
  # Install system dependencies for XML libraries
8
8
  RUN apt-get update && apt-get install -y \
9
9
  libxml2-dev \
10
10
  libxslt1-dev \
11
11
  build-essential \
12
+ git \
12
13
  && rm -rf /var/lib/apt/lists/*
13
14
 
14
15
  # Set working directory
@@ -28,4 +29,4 @@ RUN gem install bundler:2.5.22 && \
28
29
  RUN mkdir -p /app/results
29
30
 
30
31
  # Default command runs parsing and generation benchmarks (memory benchmarks disabled due to hanging in Docker)
31
- CMD ["bundle", "exec", "ruby", "exe/serialbench", "benchmark", "--formats", "xml", "json", "yaml", "toml", "--iterations", "1", "--warmup", "0", "--parsing_only"]
32
+ ENTRYPOINT ["bundle", "exec", "serialbench"]
data/docker/README.md CHANGED
@@ -4,7 +4,7 @@ This directory contains Docker infrastructure for running Serialbench across mul
4
4
 
5
5
  ## Files
6
6
 
7
- - `Dockerfile.benchmark` - Multi-stage Dockerfile for building benchmark environments
7
+ - `Dockerfile.ubuntu` - Multi-stage Dockerfile for building benchmark environments
8
8
  - `run-benchmarks.sh` - Automated script for running benchmarks across multiple Ruby versions
9
9
 
10
10
  ## Quick Start
@@ -77,7 +77,7 @@ docker-results/
77
77
  docker build \
78
78
  --build-arg RUBY_VERSION=3.3 \
79
79
  -t serialbench:ruby-3.3 \
80
- -f docker/Dockerfile.benchmark \
80
+ -f docker/Dockerfile.ubuntu \
81
81
  .
82
82
  ```
83
83
 
@@ -0,0 +1,79 @@
1
+ # Platform Validation Fix
2
+
3
+ ## Problem
4
+
5
+ GitHub Actions workflow run [18624230866](https://github.com/metanorma/serialbench/actions/runs/18624230866/job/53099708431) failed with the error:
6
+
7
+ ```
8
+ Error adding run to resultset: undefined method `platform_string' for nil
9
+ ```
10
+
11
+ This occurred when trying to add benchmark results from `artifacts/benchmark-results-macos-latest-ruby-3.2` to the weekly resultset.
12
+
13
+ ## Root Cause
14
+
15
+ The error occurred in `lib/serialbench/models/result_set.rb` line 56, where the code attempted to access `result.platform.platform_string` to check for duplicate results. When `result.platform` is `nil`, this causes a NoMethodError.
16
+
17
+ The underlying issue is that some result files were missing the `platform` section in their YAML data, causing `Result.load()` to return a Result object with a nil platform.
18
+
19
+ ## Solution Implemented
20
+
21
+ Added validation in the `ResultSet#add_result` method to check for required fields before attempting to access their properties:
22
+
23
+ ```ruby
24
+ # Validate that the result has required fields
25
+ raise ArgumentError, "Result from #{result_path} is missing platform information" if result.platform.nil?
26
+ raise ArgumentError, "Result from #{result_path} is missing environment_config" if result.environment_config.nil?
27
+ raise ArgumentError, "Result from #{result_path} is missing benchmark_config" if result.benchmark_config.nil?
28
+ ```
29
+
30
+ This provides clear, actionable error messages that indicate:
31
+ 1. Which result file has the problem
32
+ 2. Which specific field is missing
33
+
34
+ ## Testing
35
+
36
+ Created comprehensive RSpec tests in `spec/serialbench/models/result_set_spec.rb` that verify:
37
+ - Missing platform information raises appropriate error
38
+ - Missing environment_config raises appropriate error
39
+ - Missing benchmark_config raises appropriate error
40
+ - Complete results are added successfully
41
+
42
+ All tests pass.
43
+
44
+ ## Next Steps
45
+
46
+ While this fix improves error reporting, the underlying issue of why platform data is missing needs investigation:
47
+
48
+ 1. **Investigate Result Generation**: Check why some results are being created without platform data
49
+ - Review `LocalRunner#run_benchmark` in `lib/serialbench/runners/local_runner.rb`
50
+ - Review how platform data is serialized in `Platform.to_yaml`
51
+ - Check if there's a Lutaml::Model serialization issue
52
+
53
+ 2. **GitHub Actions Debugging**:
54
+ - Add debugging output to show the contents of result files before adding them to resultsets
55
+ - Verify that all benchmark runs are creating complete result files with platform data
56
+
57
+ 3. **Platform Serialization**: Ensure `Platform` model properly serializes/deserializes:
58
+ - All attributes are included in YAML output
59
+ - Default values are properly set
60
+ - No Lutaml::Model configuration issues
61
+
62
+ ## Commit
63
+
64
+ ```
65
+ fix(resultset): add validation for missing platform data in results
66
+
67
+ Previously, when adding a result to a resultset, if the result's YAML file
68
+ was missing platform information (or environment_config/benchmark_config),
69
+ the code would fail with 'undefined method platform_string for nil' when
70
+ trying to check for duplicates.
71
+
72
+ This commit adds proper validation to check for nil values before accessing
73
+ their properties, providing clear error messages that indicate which field
74
+ is missing and which result path has the issue.
75
+
76
+ Also adds comprehensive RSpec tests to verify the validation works correctly
77
+ for all three required fields: platform, environment_config, and benchmark_config.
78
+
79
+ Fixes the error seen in GitHub Actions workflow run 18624230866.
@@ -0,0 +1,91 @@
1
+ # Syck YAML Constant Fix
2
+
3
+ ## Problem Summary
4
+
5
+ GitHub Actions workflow failed with cryptic error:
6
+ ```
7
+ Error adding run to resultset: undefined method `platform_string' for nil
8
+ ```
9
+
10
+ Root cause: Benchmark results.yaml files were only 2 bytes containing `{}` instead of the expected ~10KB files with complete benchmark data.
11
+
12
+ ## Root Cause Analysis
13
+
14
+ After 4 rounds of progressive fixes, we discovered the Syck gem was overriding the YAML constant, causing Lutaml::Model's `to_yaml` method to produce empty output.
15
+
16
+ ### Fix Timeline
17
+
18
+ 1. **Fix #1 (Commit 53f1799)**: Corrected `memory_usage` → `memory` attribute name
19
+ - Result: Still produced empty `{}` files
20
+
21
+ 2. **Fix #2 (Commit fcd23f2)**: Added key_value blocks to BenchmarkResult models
22
+ - Result: Still produced empty `{}` files
23
+
24
+ 3. **Fix #3 (Commit d8fb78b)**: Added key_value blocks to all remaining models
25
+ - Result: Still produced empty `{}` files
26
+
27
+ 4. **Fix #4 (Commit 69a2bd7)**: **Restored YAML constant to Psych in LocalRunner**
28
+ - Result: ✅ **SUCCESS! 10KB files with complete data**
29
+
30
+ ## The Critical Fix
31
+
32
+ Added to `lib/serialbench/runners/local_runner.rb`:
33
+
34
+ ```ruby
35
+ # Restore YAML to use Psych for output, otherwise lutaml-model's to_yaml
36
+ # will have no output (Syck gem overrides YAML constant)
37
+ Object.const_set(:YAML, Psych)
38
+
39
+ results_file = File.join(result_dir, 'results.yaml')
40
+ results_model.to_file(results_file)
41
+ ```
42
+
43
+ ## Why This Happened
44
+
45
+ - The `_docker_execute` method in `benchmark_cli.rb` already had this fix (lines 151-154)
46
+ - Local testing with `serialbench benchmark _docker_execute` worked fine
47
+ - GHA with `serialbench environment execute` (uses LocalRunner) failed
48
+ - The Syck gem overrides the YAML constant during benchmark execution
49
+ - Without restoring it to Psych, Lutaml::Model serialization produces empty output
50
+
51
+ ## Verification
52
+
53
+ From successful GHA run #51:
54
+
55
+ ```
56
+ File size: 10258 bytes
57
+ ✅ File size OK: 10258 bytes
58
+ ✅ YAML syntax valid
59
+
60
+ ---
61
+ platform:
62
+ platform_string: local-3.4.7
63
+ kind: local
64
+ os: macos
65
+ arch: arm64
66
+ ruby_build_tag: 3.4.7
67
+ metadata:
68
+ benchmark_config_path: config/benchmarks/short.yml
69
+ environment_config_path: config/environments/ci-ruby-3.4.yml
70
+ tags:
71
+ - local
72
+ - macos
73
+ - arm64
74
+ - ruby-3.4
75
+ [...]
76
+ ```
77
+
78
+ ## Impact
79
+
80
+ This fix ensures:
81
+ 1. Benchmark results are properly serialized in all execution contexts
82
+ 2. ResultSet aggregation works correctly
83
+ 3. No more cryptic nil errors
84
+ 4. Complete benchmark data flows through the entire pipeline
85
+
86
+ ## Lessons Learned
87
+
88
+ 1. Always check for gem interference with global constants
89
+ 2. The Syck gem is a known source of YAML constant conflicts
90
+ 3. Test in the same execution context as production (LocalRunner vs DockerRunner behavior differs)
91
+ 4. Even with proper model definitions, external factors can break serialization