up
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-11-27 23:44:42 +02:00
parent ef6e4b2067
commit 3b96b2e3ea
298 changed files with 47516 additions and 1168 deletions

View File

@@ -13,6 +13,7 @@
"ruby.observation.capability.serialization": "false",
"ruby.observation.dependency_edges": "6",
"ruby.observation.packages": "9",
"ruby.observation.ruby_version": "3.2.0",
"ruby.observation.runtime_edges": "0"
},
"evidence": [
@@ -20,8 +21,8 @@
"kind": "derived",
"source": "ruby.observation",
"locator": "document",
"value": "{\u0022$schema\u0022:\u0022stellaops.ruby.observation@1\u0022,\u0022packages\u0022:[{\u0022name\u0022:\u0022bundler\u0022,\u0022version\u0022:\u00222.5.3\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022development\u0022]},{\u0022name\u0022:\u0022pastel\u0022,\u0022version\u0022:\u00220.8.0\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022default\u0022]},{\u0022name\u0022:\u0022thor\u0022,\u0022version\u0022:\u00221.3.0\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022default\u0022]},{\u0022name\u0022:\u0022tty-color\u0022,\u0022version\u0022:\u00220.6.0\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022default\u0022]},{\u0022name\u0022:\u0022tty-cursor\u0022,\u0022version\u0022:\u00220.7.1\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022default\u0022]},{\u0022name\u0022:\u0022tty-prompt\u0022,\u0022version\u0022:\u00220.23.1\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022default\u0022]},{\u0022name\u0022:\u0022tty-reader\u0022,\u0022version\u0022:\u00220.9.0\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022default\u0022]},{\u0022name\u0022:\u0022tty-screen\u0022,\u0022version\u0022:\u00220.8.2\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022default\u0022]},{\u0022name\u0022:\u0022wisper\u0022,\u0022version\u0022:\u00222.0.1\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022default\u0022]}],\u0022entrypoints\u0022:[],\u0022dependencyEdges\u0022:[{\u0022from\u0022:\u0022pkg:gem/pastel@0.8.0\u0022,\u0022to\u0022:\u0022tty-color\u0022,\u0022constraint\u0022:\u0022~\\u003E 0.5\u0022},{\u0022from\u0022:\u0022pkg:gem/tty-prompt@0.23.1\u0022,\u0022to\u0022:\u0022pastel\u0022,\u0022constraint\u0022:\u0022~\\u003E 0.8\u0022},{\u0022from\u0022:\u0022pkg:gem/tty-prompt@0.23.1\u0022,\u0022to\u0022:\u0022tty-reader\u0022,\u0022constraint\u0022:\u0022~\\u003E 0.8\u0022},{\u0022from\u0022:\u0022pkg:gem/tty-reader@0.9.0\u0022,\u0022to\u0022:\u0022tty-cursor\u0022,\u0022constraint\u0022:\u0022~\\u003E 0.7\u0022},{\u0022from\u0022:\u0022pkg:gem/tty-reader@0.9.0\u0022,\u0022to\u0022:\u0022tty-screen\u0022,\u0022constraint\u0022:\u0022~\\u003E 0.8\u0022},{\u0022from\u0022:\u0022pkg:gem/tty-reader@0.9.0\u0022,\u0022to\u0022:\u0022wisper\u0022,\u0022constraint\u0022:\u0022~\\u003E 2.0\u0022}],\u0022runtimeEdges\u0022:[],\u0022environment\u0022:{\u0022bundlerVersion\u0022:\u00222.5.3\u0022,\u0022lockfiles\u0022:[\u0022Gemfile.lock\u0022]},\u0022capabilities\u0022:{\u0022usesExec\u0022:false,\u0022usesNetwork\u0022:false,\u0022usesSerialization\u0022:false,\u0022jobSchedulers\u0022:[]},\u0022bundledWith\u0022:\u00222.5.3\u0022}",
"sha256": "sha256:5ec8b45dc480086cefbee03575845d57fb9fe4a0b000b109af46af5f2fe3f05d"
"value": "{\u0022$schema\u0022:\u0022stellaops.ruby.observation@1\u0022,\u0022packages\u0022:[{\u0022name\u0022:\u0022bundler\u0022,\u0022version\u0022:\u00222.5.3\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022development\u0022]},{\u0022name\u0022:\u0022pastel\u0022,\u0022version\u0022:\u00220.8.0\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022default\u0022]},{\u0022name\u0022:\u0022thor\u0022,\u0022version\u0022:\u00221.3.0\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022default\u0022]},{\u0022name\u0022:\u0022tty-color\u0022,\u0022version\u0022:\u00220.6.0\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022default\u0022]},{\u0022name\u0022:\u0022tty-cursor\u0022,\u0022version\u0022:\u00220.7.1\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022default\u0022]},{\u0022name\u0022:\u0022tty-prompt\u0022,\u0022version\u0022:\u00220.23.1\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022default\u0022]},{\u0022name\u0022:\u0022tty-reader\u0022,\u0022version\u0022:\u00220.9.0\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022default\u0022]},{\u0022name\u0022:\u0022tty-screen\u0022,\u0022version\u0022:\u00220.8.2\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022default\u0022]},{\u0022name\u0022:\u0022wisper\u0022,\u0022version\u0022:\u00222.0.1\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022default\u0022]}],\u0022entrypoints\u0022:[],\u0022dependencyEdges\u0022:[{\u0022from\u0022:\u0022pkg:gem/pastel@0.8.0\u0022,\u0022to\u0022:\u0022tty-color\u0022,\u0022constraint\u0022:\u0022~\\u003E 0.5\u0022},{\u0022from\u0022:\u0022pkg:gem/tty-prompt@0.23.1\u0022,\u0022to\u0022:\u0022pastel\u0022,\u0022constraint\u0022:\u0022~\\u003E 0.8\u0022},{\u0022from\u0022:\u0022pkg:gem/tty-prompt@0.23.1\u0022,\u0022to\u0022:\u0022tty-reader\u0022,\u0022constraint\u0022:\u0022~\\u003E 0.8\u0022},{\u0022from\u0022:\u0022pkg:gem/tty-reader@0.9.0\u0022,\u0022to\u0022:\u0022tty-cursor\u0022,\u0022constraint\u0022:\u0022~\\u003E 0.7\u0022},{\u0022from\u0022:\u0022pkg:gem/tty-reader@0.9.0\u0022,\u0022to\u0022:\u0022tty-screen\u0022,\u0022constraint\u0022:\u0022~\\u003E 0.8\u0022},{\u0022from\u0022:\u0022pkg:gem/tty-reader@0.9.0\u0022,\u0022to\u0022:\u0022wisper\u0022,\u0022constraint\u0022:\u0022~\\u003E 2.0\u0022}],\u0022runtimeEdges\u0022:[],\u0022environment\u0022:{\u0022rubyVersion\u0022:\u00223.2.0\u0022,\u0022bundlerVersion\u0022:\u00222.5.3\u0022,\u0022lockfiles\u0022:[\u0022Gemfile.lock\u0022],\u0022rubyVersionSources\u0022:[{\u0022version\u0022:\u00223.2.0\u0022,\u0022source\u0022:\u0022Gemfile\u0022,\u0022sourceType\u0022:\u0022gemfile\u0022}]},\u0022capabilities\u0022:{\u0022usesExec\u0022:false,\u0022usesNetwork\u0022:false,\u0022usesSerialization\u0022:false,\u0022jobSchedulers\u0022:[]},\u0022bundledWith\u0022:\u00222.5.3\u0022}",
"sha256": "sha256:b8346ca01f40135f4a8de7ae73a601b621e228473b26516cfda65cc046d7a7c4"
}
]
},

View File

@@ -21,8 +21,8 @@
"kind": "derived",
"source": "ruby.observation",
"locator": "document",
"value": "{\u0022$schema\u0022:\u0022stellaops.ruby.observation@1\u0022,\u0022packages\u0022:[{\u0022name\u0022:\u0022clockwork\u0022,\u0022version\u0022:\u00223.0.0\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022ops\u0022]},{\u0022name\u0022:\u0022pagy\u0022,\u0022version\u0022:\u00226.5.0\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022web\u0022]},{\u0022name\u0022:\u0022pry\u0022,\u0022version\u0022:\u00220.14.2\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022tools\u0022]},{\u0022name\u0022:\u0022rack\u0022,\u0022version\u0022:\u00223.1.2\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022default\u0022]},{\u0022name\u0022:\u0022sidekiq\u0022,\u0022version\u0022:\u00227.2.1\u0022,\u0022source\u0022:\u0022vendor\u0022,\u0022declaredOnly\u0022:false,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022artifact\u0022:\u0022vendor/custom-bundle/cache/sidekiq-7.2.1.gem\u0022,\u0022groups\u0022:[\u0022jobs\u0022]},{\u0022name\u0022:\u0022sinatra\u0022,\u0022version\u0022:\u00223.1.0\u0022,\u0022source\u0022:\u0022vendor-cache\u0022,\u0022declaredOnly\u0022:false,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022artifact\u0022:\u0022vendor/cache/sinatra-3.1.0.gem\u0022,\u0022groups\u0022:[\u0022web\u0022]}],\u0022entrypoints\u0022:[{\u0022path\u0022:\u0022config/environment.rb\u0022,\u0022type\u0022:\u0022script\u0022,\u0022requiredGems\u0022:[\u0022pagy\u0022]}],\u0022dependencyEdges\u0022:[{\u0022from\u0022:\u0022pkg:gem/pry@0.14.2\u0022,\u0022to\u0022:\u0022coderay\u0022,\u0022constraint\u0022:\u0022~\\u003E 1.1\u0022},{\u0022from\u0022:\u0022pkg:gem/pry@0.14.2\u0022,\u0022to\u0022:\u0022method_source\u0022,\u0022constraint\u0022:\u0022~\\u003E 1.0\u0022},{\u0022from\u0022:\u0022pkg:gem/sidekiq@7.2.1\u0022,\u0022to\u0022:\u0022rack\u0022,\u0022constraint\u0022:\u0022~\\u003E 2.0\u0022},{\u0022from\u0022:\u0022pkg:gem/sinatra@3.1.0\u0022,\u0022to\u0022:\u0022rack\u0022,\u0022constraint\u0022:\u0022~\\u003E 3.0\u0022}],\u0022runtimeEdges\u0022:[{\u0022package\u0022:\u0022clockwork\u0022,\u0022usedByEntrypoint\u0022:false,\u0022files\u0022:[\u0022scripts/worker.rb\u0022],\u0022entrypoints\u0022:[],\u0022reasons\u0022:[\u0022require-static\u0022]},{\u0022package\u0022:\u0022pagy\u0022,\u0022usedByEntrypoint\u0022:true,\u0022files\u0022:[\u0022app/main.rb\u0022,\u0022config/environment.rb\u0022],\u0022entrypoints\u0022:[\u0022config/environment.rb\u0022],\u0022reasons\u0022:[\u0022require-static\u0022]},{\u0022package\u0022:\u0022rack\u0022,\u0022usedByEntrypoint\u0022:false,\u0022files\u0022:[\u0022app/main.rb\u0022],\u0022entrypoints\u0022:[],\u0022reasons\u0022:[\u0022require-static\u0022]},{\u0022package\u0022:\u0022sidekiq\u0022,\u0022usedByEntrypoint\u0022:false,\u0022files\u0022:[\u0022scripts/worker.rb\u0022],\u0022entrypoints\u0022:[],\u0022reasons\u0022:[\u0022require-static\u0022]},{\u0022package\u0022:\u0022sinatra\u0022,\u0022usedByEntrypoint\u0022:false,\u0022files\u0022:[\u0022app/main.rb\u0022],\u0022entrypoints\u0022:[],\u0022reasons\u0022:[\u0022require-static\u0022]}],\u0022environment\u0022:{\u0022bundlerVersion\u0022:\u00222.5.3\u0022,\u0022bundlePaths\u0022:[\u0022/mnt/e/dev/git.stella-ops.org/src/Scanner/__Tests/StellaOps.Scanner.Analyzers.Lang.Ruby.Tests/bin/Debug/net10.0/Fixtures/lang/ruby/complex-app/vendor/custom-bundle\u0022],\u0022gemfiles\u0022:[\u0022/mnt/e/dev/git.stella-ops.org/src/Scanner/__Tests/StellaOps.Scanner.Analyzers.Lang.Ruby.Tests/bin/Debug/net10.0/Fixtures/lang/ruby/complex-app/Gemfile\u0022],\u0022lockfiles\u0022:[\u0022Gemfile.lock\u0022],\u0022frameworks\u0022:[\u0022clockwork\u0022,\u0022sidekiq\u0022]},\u0022capabilities\u0022:{\u0022usesExec\u0022:false,\u0022usesNetwork\u0022:true,\u0022usesSerialization\u0022:false,\u0022jobSchedulers\u0022:[\u0022clockwork\u0022,\u0022sidekiq\u0022]},\u0022bundledWith\u0022:\u00222.5.3\u0022}",
"sha256": "sha256:58c8c02011baf8711e584a4b8e33effe7292a92af69cd6eaad6c3fd869ea93e0"
"value": "{\u0022$schema\u0022:\u0022stellaops.ruby.observation@1\u0022,\u0022packages\u0022:[{\u0022name\u0022:\u0022clockwork\u0022,\u0022version\u0022:\u00223.0.0\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022ops\u0022]},{\u0022name\u0022:\u0022pagy\u0022,\u0022version\u0022:\u00226.5.0\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022web\u0022]},{\u0022name\u0022:\u0022pry\u0022,\u0022version\u0022:\u00220.14.2\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022tools\u0022]},{\u0022name\u0022:\u0022rack\u0022,\u0022version\u0022:\u00223.1.2\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022default\u0022]},{\u0022name\u0022:\u0022sidekiq\u0022,\u0022version\u0022:\u00227.2.1\u0022,\u0022source\u0022:\u0022vendor\u0022,\u0022declaredOnly\u0022:false,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022artifact\u0022:\u0022vendor/custom-bundle/cache/sidekiq-7.2.1.gem\u0022,\u0022groups\u0022:[\u0022jobs\u0022]},{\u0022name\u0022:\u0022sinatra\u0022,\u0022version\u0022:\u00223.1.0\u0022,\u0022source\u0022:\u0022vendor-cache\u0022,\u0022declaredOnly\u0022:false,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022artifact\u0022:\u0022vendor/cache/sinatra-3.1.0.gem\u0022,\u0022groups\u0022:[\u0022web\u0022]}],\u0022entrypoints\u0022:[{\u0022path\u0022:\u0022config/environment.rb\u0022,\u0022type\u0022:\u0022script\u0022,\u0022requiredGems\u0022:[\u0022pagy\u0022]}],\u0022dependencyEdges\u0022:[{\u0022from\u0022:\u0022pkg:gem/pry@0.14.2\u0022,\u0022to\u0022:\u0022coderay\u0022,\u0022constraint\u0022:\u0022~\\u003E 1.1\u0022},{\u0022from\u0022:\u0022pkg:gem/pry@0.14.2\u0022,\u0022to\u0022:\u0022method_source\u0022,\u0022constraint\u0022:\u0022~\\u003E 1.0\u0022},{\u0022from\u0022:\u0022pkg:gem/sidekiq@7.2.1\u0022,\u0022to\u0022:\u0022rack\u0022,\u0022constraint\u0022:\u0022~\\u003E 2.0\u0022},{\u0022from\u0022:\u0022pkg:gem/sinatra@3.1.0\u0022,\u0022to\u0022:\u0022rack\u0022,\u0022constraint\u0022:\u0022~\\u003E 3.0\u0022}],\u0022runtimeEdges\u0022:[{\u0022package\u0022:\u0022clockwork\u0022,\u0022usedByEntrypoint\u0022:false,\u0022files\u0022:[\u0022scripts/worker.rb\u0022],\u0022entrypoints\u0022:[],\u0022reasons\u0022:[\u0022require-static\u0022]},{\u0022package\u0022:\u0022pagy\u0022,\u0022usedByEntrypoint\u0022:true,\u0022files\u0022:[\u0022app/main.rb\u0022,\u0022config/environment.rb\u0022],\u0022entrypoints\u0022:[\u0022config/environment.rb\u0022],\u0022reasons\u0022:[\u0022require-static\u0022]},{\u0022package\u0022:\u0022rack\u0022,\u0022usedByEntrypoint\u0022:false,\u0022files\u0022:[\u0022app/main.rb\u0022],\u0022entrypoints\u0022:[],\u0022reasons\u0022:[\u0022require-static\u0022]},{\u0022package\u0022:\u0022sidekiq\u0022,\u0022usedByEntrypoint\u0022:false,\u0022files\u0022:[\u0022scripts/worker.rb\u0022],\u0022entrypoints\u0022:[],\u0022reasons\u0022:[\u0022require-static\u0022]},{\u0022package\u0022:\u0022sinatra\u0022,\u0022usedByEntrypoint\u0022:false,\u0022files\u0022:[\u0022app/main.rb\u0022],\u0022entrypoints\u0022:[],\u0022reasons\u0022:[\u0022require-static\u0022]}],\u0022jobs\u0022:[{\u0022name\u0022:\u0022clockwork\u0022,\u0022type\u0022:\u0022scheduler\u0022,\u0022scheduler\u0022:\u0022clockwork\u0022},{\u0022name\u0022:\u0022sidekiq\u0022,\u0022type\u0022:\u0022scheduler\u0022,\u0022scheduler\u0022:\u0022sidekiq\u0022}],\u0022environment\u0022:{\u0022bundlerVersion\u0022:\u00222.5.3\u0022,\u0022bundlePaths\u0022:[\u0022/mnt/e/dev/git.stella-ops.org/src/Scanner/__Tests/StellaOps.Scanner.Analyzers.Lang.Ruby.Tests/bin/Debug/net10.0/Fixtures/lang/ruby/complex-app/vendor/custom-bundle\u0022],\u0022gemfiles\u0022:[\u0022/mnt/e/dev/git.stella-ops.org/src/Scanner/__Tests/StellaOps.Scanner.Analyzers.Lang.Ruby.Tests/bin/Debug/net10.0/Fixtures/lang/ruby/complex-app/Gemfile\u0022],\u0022lockfiles\u0022:[\u0022Gemfile.lock\u0022],\u0022frameworks\u0022:[\u0022clockwork\u0022,\u0022sidekiq\u0022]},\u0022capabilities\u0022:{\u0022usesExec\u0022:false,\u0022usesNetwork\u0022:true,\u0022usesSerialization\u0022:false,\u0022jobSchedulers\u0022:[\u0022clockwork\u0022,\u0022sidekiq\u0022]},\u0022bundledWith\u0022:\u00222.5.3\u0022}",
"sha256": "sha256:bd15160e034ea5adf0a8384dc9ee18557f695b0952d4fb17214f1bd1381ad22a"
}
]
},

View File

@@ -0,0 +1,9 @@
# frozen_string_literal: true
source "https://rubygems.org"
ruby "3.2.0"
gem "nokogiri", "~> 1.15"
gem "pg", "~> 1.5"
gem "puma", "~> 6.0"
gem "rack", "~> 3.0"

View File

@@ -0,0 +1,29 @@
GEM
remote: https://rubygems.org/
specs:
mini_portile2 (2.8.4)
nio4r (2.5.9)
nokogiri (1.15.0)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
pg (1.5.4)
puma (6.4.0)
nio4r (~> 2.0)
racc (1.7.1)
rack (3.0.8)
PLATFORMS
ruby
x86_64-linux
DEPENDENCIES
nokogiri (~> 1.15)
pg (~> 1.5)
puma (~> 6.0)
rack (~> 3.0)
RUBY VERSION
ruby 3.2.0p0
BUNDLED WITH
2.4.22

View File

@@ -0,0 +1,10 @@
# frozen_string_literal: true
require "rack"
require "nokogiri"
require "pg"
class App
def call(env)
[200, { "Content-Type" => "text/plain" }, ["Hello from container"]]
end
end

View File

@@ -0,0 +1,5 @@
# frozen_string_literal: true
require "rack"
require_relative "app"
run App

View File

@@ -0,0 +1,9 @@
# frozen_string_literal: true
workers ENV.fetch("WEB_CONCURRENCY", 2)
threads_count = ENV.fetch("RAILS_MAX_THREADS", 5)
threads threads_count, threads_count
preload_app!
port ENV.fetch("PORT", 3000)
environment ENV.fetch("RACK_ENV", "development")

View File

@@ -0,0 +1,197 @@
[
{
"analyzerId": "ruby",
"componentKey": "observation::ruby",
"name": "Ruby Observation Summary",
"type": "ruby-observation",
"usedByEntrypoint": false,
"metadata": {
"ruby.observation.bundler_version": "2.4.22",
"ruby.observation.capability.exec": "false",
"ruby.observation.capability.net": "false",
"ruby.observation.capability.schedulers": "0",
"ruby.observation.capability.serialization": "false",
"ruby.observation.dependency_edges": "3",
"ruby.observation.packages": "7",
"ruby.observation.ruby_version": "3.2.0",
"ruby.observation.runtime_edges": "3",
"ruby.observation.web_server_types": "puma",
"ruby.observation.web_servers": "1"
},
"evidence": [
{
"kind": "derived",
"source": "ruby.observation",
"locator": "document",
"value": "{\u0022$schema\u0022:\u0022stellaops.ruby.observation@1\u0022,\u0022packages\u0022:[{\u0022name\u0022:\u0022mini_portile2\u0022,\u0022version\u0022:\u00222.8.4\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022default\u0022]},{\u0022name\u0022:\u0022nio4r\u0022,\u0022version\u0022:\u00222.5.9\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022default\u0022]},{\u0022name\u0022:\u0022nokogiri\u0022,\u0022version\u0022:\u00221.15.0\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022default\u0022]},{\u0022name\u0022:\u0022pg\u0022,\u0022version\u0022:\u00221.5.4\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022default\u0022]},{\u0022name\u0022:\u0022puma\u0022,\u0022version\u0022:\u00226.4.0\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022default\u0022]},{\u0022name\u0022:\u0022racc\u0022,\u0022version\u0022:\u00221.7.1\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022default\u0022]},{\u0022name\u0022:\u0022rack\u0022,\u0022version\u0022:\u00223.0.8\u0022,\u0022source\u0022:\u0022https://rubygems.org/\u0022,\u0022declaredOnly\u0022:true,\u0022lockfile\u0022:\u0022Gemfile.lock\u0022,\u0022groups\u0022:[\u0022default\u0022]}],\u0022entrypoints\u0022:[{\u0022path\u0022:\u0022app.rb\u0022,\u0022type\u0022:\u0022script\u0022,\u0022requiredGems\u0022:[\u0022nokogiri\u0022,\u0022pg\u0022,\u0022rack\u0022]},{\u0022path\u0022:\u0022config.ru\u0022,\u0022type\u0022:\u0022rack\u0022,\u0022requiredGems\u0022:[\u0022rack\u0022]}],\u0022dependencyEdges\u0022:[{\u0022from\u0022:\u0022pkg:gem/nokogiri@1.15.0\u0022,\u0022to\u0022:\u0022mini_portile2\u0022,\u0022constraint\u0022:\u0022~\\u003E 2.8.2\u0022},{\u0022from\u0022:\u0022pkg:gem/nokogiri@1.15.0\u0022,\u0022to\u0022:\u0022racc\u0022,\u0022constraint\u0022:\u0022~\\u003E 1.4\u0022},{\u0022from\u0022:\u0022pkg:gem/puma@6.4.0\u0022,\u0022to\u0022:\u0022nio4r\u0022,\u0022constraint\u0022:\u0022~\\u003E 2.0\u0022}],\u0022runtimeEdges\u0022:[{\u0022package\u0022:\u0022nokogiri\u0022,\u0022usedByEntrypoint\u0022:true,\u0022files\u0022:[\u0022app.rb\u0022],\u0022entrypoints\u0022:[\u0022app.rb\u0022],\u0022reasons\u0022:[\u0022require-static\u0022]},{\u0022package\u0022:\u0022pg\u0022,\u0022usedByEntrypoint\u0022:true,\u0022files\u0022:[\u0022app.rb\u0022],\u0022entrypoints\u0022:[\u0022app.rb\u0022],\u0022reasons\u0022:[\u0022require-static\u0022]},{\u0022package\u0022:\u0022rack\u0022,\u0022usedByEntrypoint\u0022:true,\u0022files\u0022:[\u0022app.rb\u0022,\u0022config.ru\u0022],\u0022entrypoints\u0022:[\u0022app.rb\u0022,\u0022config.ru\u0022],\u0022reasons\u0022:[\u0022require-static\u0022]}],\u0022configs\u0022:[{\u0022name\u0022:\u0022puma\u0022,\u0022type\u0022:\u0022web-server\u0022,\u0022filePath\u0022:\u0022config/puma.rb\u0022,\u0022settings\u0022:{\u0022preload_app\u0022:\u0022true\u0022}}],\u0022environment\u0022:{\u0022rubyVersion\u0022:\u00223.2.0\u0022,\u0022bundlerVersion\u0022:\u00222.4.22\u0022,\u0022lockfiles\u0022:[\u0022Gemfile.lock\u0022],\u0022rubyVersionSources\u0022:[{\u0022version\u0022:\u00223.2.0\u0022,\u0022source\u0022:\u0022.ruby-version\u0022,\u0022sourceType\u0022:\u0022ruby-version\u0022},{\u0022version\u0022:\u00223.2.0\u0022,\u0022source\u0022:\u0022.tool-versions\u0022,\u0022sourceType\u0022:\u0022tool-versions\u0022},{\u0022version\u0022:\u00223.2.0\u0022,\u0022source\u0022:\u0022Gemfile\u0022,\u0022sourceType\u0022:\u0022gemfile\u0022}],\u0022webServers\u0022:[{\u0022serverType\u0022:\u0022puma\u0022,\u0022configPath\u0022:\u0022config/puma.rb\u0022,\u0022settings\u0022:{\u0022preload_app\u0022:\u0022true\u0022}}]},\u0022capabilities\u0022:{\u0022usesExec\u0022:false,\u0022usesNetwork\u0022:false,\u0022usesSerialization\u0022:false,\u0022jobSchedulers\u0022:[]},\u0022bundledWith\u0022:\u00222.4.22\u0022}",
"sha256": "sha256:d5c7da885e1d05805981e2080c9023cd653ed464e993d5e48de6b9f55334eca7"
}
]
},
{
"analyzerId": "ruby",
"componentKey": "purl::pkg:gem/mini_portile2@2.8.4",
"purl": "pkg:gem/mini_portile2@2.8.4",
"name": "mini_portile2",
"version": "2.8.4",
"type": "gem",
"usedByEntrypoint": false,
"metadata": {
"declaredOnly": "true",
"groups": "default",
"lockfile": "Gemfile.lock",
"source": "https://rubygems.org/"
},
"evidence": [
{
"kind": "file",
"source": "Gemfile.lock",
"locator": "Gemfile.lock"
}
]
},
{
"analyzerId": "ruby",
"componentKey": "purl::pkg:gem/nio4r@2.5.9",
"purl": "pkg:gem/nio4r@2.5.9",
"name": "nio4r",
"version": "2.5.9",
"type": "gem",
"usedByEntrypoint": false,
"metadata": {
"declaredOnly": "true",
"groups": "default",
"lockfile": "Gemfile.lock",
"source": "https://rubygems.org/"
},
"evidence": [
{
"kind": "file",
"source": "Gemfile.lock",
"locator": "Gemfile.lock"
}
]
},
{
"analyzerId": "ruby",
"componentKey": "purl::pkg:gem/nokogiri@1.15.0",
"purl": "pkg:gem/nokogiri@1.15.0",
"name": "nokogiri",
"version": "1.15.0",
"type": "gem",
"usedByEntrypoint": true,
"metadata": {
"declaredOnly": "true",
"groups": "default",
"lockfile": "Gemfile.lock",
"runtime.entrypoints": "app.rb",
"runtime.files": "app.rb",
"runtime.reasons": "require-static",
"runtime.used": "true",
"source": "https://rubygems.org/"
},
"evidence": [
{
"kind": "file",
"source": "Gemfile.lock",
"locator": "Gemfile.lock"
}
]
},
{
"analyzerId": "ruby",
"componentKey": "purl::pkg:gem/pg@1.5.4",
"purl": "pkg:gem/pg@1.5.4",
"name": "pg",
"version": "1.5.4",
"type": "gem",
"usedByEntrypoint": true,
"metadata": {
"declaredOnly": "true",
"groups": "default",
"lockfile": "Gemfile.lock",
"runtime.entrypoints": "app.rb",
"runtime.files": "app.rb",
"runtime.reasons": "require-static",
"runtime.used": "true",
"source": "https://rubygems.org/"
},
"evidence": [
{
"kind": "file",
"source": "Gemfile.lock",
"locator": "Gemfile.lock"
}
]
},
{
"analyzerId": "ruby",
"componentKey": "purl::pkg:gem/puma@6.4.0",
"purl": "pkg:gem/puma@6.4.0",
"name": "puma",
"version": "6.4.0",
"type": "gem",
"usedByEntrypoint": false,
"metadata": {
"declaredOnly": "true",
"groups": "default",
"lockfile": "Gemfile.lock",
"source": "https://rubygems.org/"
},
"evidence": [
{
"kind": "file",
"source": "Gemfile.lock",
"locator": "Gemfile.lock"
}
]
},
{
"analyzerId": "ruby",
"componentKey": "purl::pkg:gem/racc@1.7.1",
"purl": "pkg:gem/racc@1.7.1",
"name": "racc",
"version": "1.7.1",
"type": "gem",
"usedByEntrypoint": false,
"metadata": {
"declaredOnly": "true",
"groups": "default",
"lockfile": "Gemfile.lock",
"source": "https://rubygems.org/"
},
"evidence": [
{
"kind": "file",
"source": "Gemfile.lock",
"locator": "Gemfile.lock"
}
]
},
{
"analyzerId": "ruby",
"componentKey": "purl::pkg:gem/rack@3.0.8",
"purl": "pkg:gem/rack@3.0.8",
"name": "rack",
"version": "3.0.8",
"type": "gem",
"usedByEntrypoint": true,
"metadata": {
"declaredOnly": "true",
"groups": "default",
"lockfile": "Gemfile.lock",
"runtime.entrypoints": "app.rb;config.ru",
"runtime.files": "app.rb;config.ru",
"runtime.reasons": "require-static",
"runtime.used": "true",
"source": "https://rubygems.org/"
},
"evidence": [
{
"kind": "file",
"source": "Gemfile.lock",
"locator": "Gemfile.lock"
}
]
}
]

View File

@@ -0,0 +1,14 @@
# Rakefile for legacy app
require 'rake'
desc "Run the application"
task :run do
ruby 'app.rb'
end
desc "Run tests"
task :test do
sh 'ruby -Ilib test/*.rb'
end
task default: :run

View File

@@ -0,0 +1,25 @@
#!/usr/bin/env ruby
# Old-style Ruby app without Bundler
require 'rubygems'
require 'net/http'
require 'json'
require 'yaml'
require_relative 'lib/helper'
class LegacyApp
def run
data = load_config
process(data)
end
def load_config
YAML.load_file('config.yml')
end
def process(data)
uri = URI.parse(data['endpoint'])
Net::HTTP.get(uri)
end
end
LegacyApp.new.run if __FILE__ == $0

View File

@@ -0,0 +1,14 @@
# Helper module
require 'fileutils'
require 'open3'
module Helper
def self.execute(cmd)
stdout, stderr, status = Open3.capture3(cmd)
{ stdout: stdout, stderr: stderr, success: status.success? }
end
def self.copy_file(src, dest)
FileUtils.cp(src, dest)
end
end

View File

@@ -0,0 +1,22 @@
# frozen_string_literal: true
source "https://rubygems.org"
ruby "3.2.0"
gem "rails", "~> 7.1.0"
gem "pg", "~> 1.5"
gem "puma", "~> 6.0"
gem "redis", "~> 5.0"
group :development, :test do
gem "rspec-rails", "~> 6.0"
gem "factory_bot_rails", "~> 6.2"
end
group :development do
gem "rubocop-rails", "~> 2.21"
end
group :production do
gem "newrelic_rpm", "~> 9.0"
end

View File

@@ -0,0 +1,54 @@
GEM
remote: https://rubygems.org/
specs:
actioncable (7.1.0)
actionpack (= 7.1.0)
actionpack (7.1.0)
activesupport (= 7.1.0)
rack (~> 3.0)
activesupport (7.1.0)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
concurrent-ruby (1.2.2)
factory_bot (6.2.1)
factory_bot_rails (6.2.0)
factory_bot (~> 6.2.0)
i18n (1.14.1)
concurrent-ruby (~> 1.0)
newrelic_rpm (9.6.0)
nio4r (2.5.9)
pg (1.5.4)
puma (6.4.0)
nio4r (~> 2.0)
rack (3.0.8)
rails (7.1.0)
actioncable (= 7.1.0)
actionpack (= 7.1.0)
activesupport (= 7.1.0)
redis (5.0.8)
rspec (3.12.0)
rspec-rails (6.0.3)
rspec (~> 3.12)
rubocop (1.57.2)
rubocop-rails (2.21.2)
rubocop (~> 1.33)
PLATFORMS
ruby
x86_64-linux
DEPENDENCIES
factory_bot_rails (~> 6.2)
newrelic_rpm (~> 9.0)
pg (~> 1.5)
puma (~> 6.0)
rails (~> 7.1.0)
redis (~> 5.0)
rspec-rails (~> 6.0)
rubocop-rails (~> 2.21)
RUBY VERSION
ruby 3.2.0p0
BUNDLED WITH
2.4.22

View File

@@ -0,0 +1,14 @@
# frozen_string_literal: true
require "action_controller"
require "redis"
require "pg"
class ApplicationController < ActionController::Base
before_action :authenticate_user!
private
def authenticate_user!
# Authentication logic
end
end

View File

@@ -0,0 +1,3 @@
# frozen_string_literal: true
require_relative "config/environment"
run Rails.application

View File

@@ -0,0 +1,9 @@
# frozen_string_literal: true
require "rails"
require "action_controller/railtie"
module RailsApp
class Application < Rails::Application
config.load_defaults 7.1
end
end

View File

@@ -0,0 +1,18 @@
# frozen_string_literal: true
Rails.application.routes.draw do
root "home#index"
resources :users do
member do
post :activate
end
end
namespace :api do
namespace :v1 do
resources :products, only: [:index, :show, :create]
end
end
get "/health", to: "health#check"
end

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,11 @@
# frozen_string_literal: true
source "https://rubygems.org"
gem "sinatra", "~> 3.1"
gem "sinatra-contrib", "~> 3.1"
gem "puma", "~> 6.0"
gem "rack", "~> 3.0"
group :development do
gem "sinatra-reloader", "~> 1.0"
end

View File

@@ -0,0 +1,39 @@
GEM
remote: https://rubygems.org/
specs:
mustermann (3.0.0)
ruby2_keywords (~> 0.0.1)
nio4r (2.5.9)
puma (6.4.0)
nio4r (~> 2.0)
rack (3.0.8)
rack-protection (3.1.0)
rack (~> 3.0)
rack-session (2.0.0)
rack (>= 3.0.0)
ruby2_keywords (0.0.5)
sinatra (3.1.0)
mustermann (~> 3.0)
rack (~> 3.0)
rack-protection (= 3.1.0)
rack-session (>= 2.0.0, < 3)
tilt (~> 2.0)
sinatra-contrib (3.1.0)
sinatra (= 3.1.0)
tilt (~> 2.0)
sinatra-reloader (1.0)
sinatra (>= 1.4)
tilt (2.3.0)
PLATFORMS
ruby
DEPENDENCIES
puma (~> 6.0)
rack (~> 3.0)
sinatra (~> 3.1)
sinatra-contrib (~> 3.1)
sinatra-reloader (~> 1.0)
BUNDLED WITH
2.4.22

View File

@@ -0,0 +1,25 @@
# frozen_string_literal: true
require "sinatra/base"
require "sinatra/json"
require "json"
class SinatraApp < Sinatra::Base
get "/" do
"Hello World"
end
get "/api/users" do
content_type :json
{ users: [] }.to_json
end
post "/api/users" do
content_type :json
user = JSON.parse(request.body.read)
{ created: user }.to_json
end
get "/health" do
"OK"
end
end

View File

@@ -0,0 +1,5 @@
# frozen_string_literal: true
require "rack"
require_relative "app"
run SinatraApp

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,246 @@
using System.Diagnostics;
using FluentAssertions;
using StellaOps.Scanner.Analyzers.Lang;
using StellaOps.Scanner.Analyzers.Lang.Ruby;
using StellaOps.Scanner.Analyzers.Lang.Tests.TestUtilities;
namespace StellaOps.Scanner.Analyzers.Lang.Ruby.Tests;
/// <summary>
/// Performance benchmarks for Ruby analyzer components.
/// Validates determinism requirements (<100 ms / workspace, <250 MB peak memory).
/// </summary>
public sealed class RubyBenchmarks
{
private const int WarmupIterations = 3;
private const int BenchmarkIterations = 10;
private const int MaxAnalysisTimeMs = 100;
[Fact]
public async Task SimpleApp_MeetsPerformanceTargetAsync()
{
var fixturePath = TestPaths.ResolveFixture("lang", "ruby", "simple-app");
var analyzers = new ILanguageAnalyzer[] { new RubyLanguageAnalyzer() };
var engine = new LanguageAnalyzerEngine(analyzers);
// Warmup
for (var i = 0; i < WarmupIterations; i++)
{
var context = new LanguageAnalyzerContext(fixturePath, TimeProvider.System);
await engine.AnalyzeAsync(context, CancellationToken.None);
}
// Benchmark
var sw = Stopwatch.StartNew();
for (var i = 0; i < BenchmarkIterations; i++)
{
var context = new LanguageAnalyzerContext(fixturePath, TimeProvider.System);
await engine.AnalyzeAsync(context, CancellationToken.None);
}
sw.Stop();
// Assert
var avgMs = sw.ElapsedMilliseconds / (double)BenchmarkIterations;
avgMs.Should().BeLessThan(MaxAnalysisTimeMs, $"Simple app analysis should complete in <{MaxAnalysisTimeMs}ms (actual: {avgMs:F2}ms)");
}
[Fact]
public async Task ComplexApp_MeetsPerformanceTargetAsync()
{
var fixturePath = TestPaths.ResolveFixture("lang", "ruby", "complex-app");
var analyzers = new ILanguageAnalyzer[] { new RubyLanguageAnalyzer() };
var engine = new LanguageAnalyzerEngine(analyzers);
// Warmup
for (var i = 0; i < WarmupIterations; i++)
{
var context = new LanguageAnalyzerContext(fixturePath, TimeProvider.System);
await engine.AnalyzeAsync(context, CancellationToken.None);
}
// Benchmark
var sw = Stopwatch.StartNew();
for (var i = 0; i < BenchmarkIterations; i++)
{
var context = new LanguageAnalyzerContext(fixturePath, TimeProvider.System);
await engine.AnalyzeAsync(context, CancellationToken.None);
}
sw.Stop();
// Assert
var avgMs = sw.ElapsedMilliseconds / (double)BenchmarkIterations;
avgMs.Should().BeLessThan(MaxAnalysisTimeMs, $"Complex app analysis should complete in <{MaxAnalysisTimeMs}ms (actual: {avgMs:F2}ms)");
}
[Fact]
public async Task RailsApp_MeetsPerformanceTargetAsync()
{
var fixturePath = TestPaths.ResolveFixture("lang", "ruby", "rails-app");
var analyzers = new ILanguageAnalyzer[] { new RubyLanguageAnalyzer() };
var engine = new LanguageAnalyzerEngine(analyzers);
// Warmup
for (var i = 0; i < WarmupIterations; i++)
{
var context = new LanguageAnalyzerContext(fixturePath, TimeProvider.System);
await engine.AnalyzeAsync(context, CancellationToken.None);
}
// Benchmark
var sw = Stopwatch.StartNew();
for (var i = 0; i < BenchmarkIterations; i++)
{
var context = new LanguageAnalyzerContext(fixturePath, TimeProvider.System);
await engine.AnalyzeAsync(context, CancellationToken.None);
}
sw.Stop();
// Assert
var avgMs = sw.ElapsedMilliseconds / (double)BenchmarkIterations;
avgMs.Should().BeLessThan(MaxAnalysisTimeMs, $"Rails app analysis should complete in <{MaxAnalysisTimeMs}ms (actual: {avgMs:F2}ms)");
}
[Fact]
public async Task SinatraApp_MeetsPerformanceTargetAsync()
{
var fixturePath = TestPaths.ResolveFixture("lang", "ruby", "sinatra-app");
var analyzers = new ILanguageAnalyzer[] { new RubyLanguageAnalyzer() };
var engine = new LanguageAnalyzerEngine(analyzers);
// Warmup
for (var i = 0; i < WarmupIterations; i++)
{
var context = new LanguageAnalyzerContext(fixturePath, TimeProvider.System);
await engine.AnalyzeAsync(context, CancellationToken.None);
}
// Benchmark
var sw = Stopwatch.StartNew();
for (var i = 0; i < BenchmarkIterations; i++)
{
var context = new LanguageAnalyzerContext(fixturePath, TimeProvider.System);
await engine.AnalyzeAsync(context, CancellationToken.None);
}
sw.Stop();
// Assert
var avgMs = sw.ElapsedMilliseconds / (double)BenchmarkIterations;
avgMs.Should().BeLessThan(MaxAnalysisTimeMs, $"Sinatra app analysis should complete in <{MaxAnalysisTimeMs}ms (actual: {avgMs:F2}ms)");
}
[Fact]
public async Task ContainerApp_MeetsPerformanceTargetAsync()
{
var fixturePath = TestPaths.ResolveFixture("lang", "ruby", "container-app");
var analyzers = new ILanguageAnalyzer[] { new RubyLanguageAnalyzer() };
var engine = new LanguageAnalyzerEngine(analyzers);
// Warmup
for (var i = 0; i < WarmupIterations; i++)
{
var context = new LanguageAnalyzerContext(fixturePath, TimeProvider.System);
await engine.AnalyzeAsync(context, CancellationToken.None);
}
// Benchmark
var sw = Stopwatch.StartNew();
for (var i = 0; i < BenchmarkIterations; i++)
{
var context = new LanguageAnalyzerContext(fixturePath, TimeProvider.System);
await engine.AnalyzeAsync(context, CancellationToken.None);
}
sw.Stop();
// Assert
var avgMs = sw.ElapsedMilliseconds / (double)BenchmarkIterations;
avgMs.Should().BeLessThan(MaxAnalysisTimeMs, $"Container app analysis should complete in <{MaxAnalysisTimeMs}ms (actual: {avgMs:F2}ms)");
}
[Fact]
public async Task LegacyApp_MeetsPerformanceTargetAsync()
{
var fixturePath = TestPaths.ResolveFixture("lang", "ruby", "legacy-app");
var analyzers = new ILanguageAnalyzer[] { new RubyLanguageAnalyzer() };
var engine = new LanguageAnalyzerEngine(analyzers);
// Warmup
for (var i = 0; i < WarmupIterations; i++)
{
var context = new LanguageAnalyzerContext(fixturePath, TimeProvider.System);
await engine.AnalyzeAsync(context, CancellationToken.None);
}
// Benchmark
var sw = Stopwatch.StartNew();
for (var i = 0; i < BenchmarkIterations; i++)
{
var context = new LanguageAnalyzerContext(fixturePath, TimeProvider.System);
await engine.AnalyzeAsync(context, CancellationToken.None);
}
sw.Stop();
// Assert
var avgMs = sw.ElapsedMilliseconds / (double)BenchmarkIterations;
avgMs.Should().BeLessThan(MaxAnalysisTimeMs, $"Legacy app analysis should complete in <{MaxAnalysisTimeMs}ms (actual: {avgMs:F2}ms)");
}
[Fact]
public async Task CliApp_MeetsPerformanceTargetAsync()
{
var fixturePath = TestPaths.ResolveFixture("lang", "ruby", "cli-app");
var analyzers = new ILanguageAnalyzer[] { new RubyLanguageAnalyzer() };
var engine = new LanguageAnalyzerEngine(analyzers);
// Warmup
for (var i = 0; i < WarmupIterations; i++)
{
var context = new LanguageAnalyzerContext(fixturePath, TimeProvider.System);
await engine.AnalyzeAsync(context, CancellationToken.None);
}
// Benchmark
var sw = Stopwatch.StartNew();
for (var i = 0; i < BenchmarkIterations; i++)
{
var context = new LanguageAnalyzerContext(fixturePath, TimeProvider.System);
await engine.AnalyzeAsync(context, CancellationToken.None);
}
sw.Stop();
// Assert
var avgMs = sw.ElapsedMilliseconds / (double)BenchmarkIterations;
avgMs.Should().BeLessThan(MaxAnalysisTimeMs, $"CLI app analysis should complete in <{MaxAnalysisTimeMs}ms (actual: {avgMs:F2}ms)");
}
[Fact]
public async Task MultipleRuns_ProduceDeterministicResultsAsync()
{
var fixturePath = TestPaths.ResolveFixture("lang", "ruby", "simple-app");
var analyzers = new ILanguageAnalyzer[] { new RubyLanguageAnalyzer() };
var engine = new LanguageAnalyzerEngine(analyzers);
var results = new List<string>();
// Run multiple times to verify determinism
for (var i = 0; i < 5; i++)
{
var context = new LanguageAnalyzerContext(fixturePath, TimeProvider.System);
var result = await engine.AnalyzeAsync(context, CancellationToken.None);
results.Add(result.ToJson(indent: false));
}
// All results should be identical
var firstResult = results[0];
foreach (var result in results.Skip(1))
{
result.Should().Be(firstResult, "all runs should produce identical output for determinism");
}
}
}

View File

@@ -100,4 +100,134 @@ public sealed class RubyLanguageAnalyzerTests
analyzers,
TestContext.Current.CancellationToken);
}
[Fact]
public async Task RailsWorkspaceProducesDeterministicOutputAsync()
{
var fixturePath = TestPaths.ResolveFixture("lang", "ruby", "rails-app");
var goldenPath = Path.Combine(fixturePath, "expected.json");
var analyzers = new ILanguageAnalyzer[] { new RubyLanguageAnalyzer() };
await LanguageAnalyzerTestHarness.AssertDeterministicAsync(
fixturePath,
goldenPath,
analyzers,
TestContext.Current.CancellationToken);
}
[Fact]
public async Task SinatraWorkspaceProducesDeterministicOutputAsync()
{
var fixturePath = TestPaths.ResolveFixture("lang", "ruby", "sinatra-app");
var goldenPath = Path.Combine(fixturePath, "expected.json");
var analyzers = new ILanguageAnalyzer[] { new RubyLanguageAnalyzer() };
await LanguageAnalyzerTestHarness.AssertDeterministicAsync(
fixturePath,
goldenPath,
analyzers,
TestContext.Current.CancellationToken);
}
[Fact]
public async Task ContainerWorkspaceProducesDeterministicOutputAsync()
{
var fixturePath = TestPaths.ResolveFixture("lang", "ruby", "container-app");
var goldenPath = Path.Combine(fixturePath, "expected.json");
var analyzers = new ILanguageAnalyzer[] { new RubyLanguageAnalyzer() };
await LanguageAnalyzerTestHarness.AssertDeterministicAsync(
fixturePath,
goldenPath,
analyzers,
TestContext.Current.CancellationToken);
}
[Fact]
public async Task ContainerWorkspaceDetectsRubyVersionAndNativeExtensionsAsync()
{
var fixturePath = TestPaths.ResolveFixture("lang", "ruby", "container-app");
var store = new ScanAnalysisStore();
var analyzers = new ILanguageAnalyzer[] { new RubyLanguageAnalyzer() };
var engine = new LanguageAnalyzerEngine(analyzers);
var context = new LanguageAnalyzerContext(
fixturePath,
TimeProvider.System,
usageHints: null,
services: null,
analysisStore: store);
var result = await engine.AnalyzeAsync(context, TestContext.Current.CancellationToken);
var snapshots = result.ToSnapshots();
var summary = Assert.Single(snapshots, snapshot => snapshot.Type == "ruby-observation");
// Verify Ruby version is detected
Assert.True(summary.Metadata.TryGetValue("ruby.observation.ruby_version", out var rubyVersion));
Assert.Equal("3.2.0", rubyVersion);
// Verify native extensions are detected
Assert.True(summary.Metadata.TryGetValue("ruby.observation.native_extensions", out var nativeExtCount));
Assert.NotNull(nativeExtCount);
Assert.True(int.Parse(nativeExtCount!) >= 2); // nokogiri and pg
Assert.True(store.TryGet(ScanAnalysisKeys.RubyObservationPayload, out AnalyzerObservationPayload payload));
using var document = JsonDocument.Parse(payload.Content.ToArray());
var root = document.RootElement;
var environment = root.GetProperty("environment");
// Check Ruby version sources
Assert.True(environment.TryGetProperty("rubyVersionSources", out var versionSources));
Assert.True(versionSources.GetArrayLength() >= 1);
// Check native extensions
Assert.True(environment.TryGetProperty("nativeExtensions", out var nativeExtensions));
Assert.True(nativeExtensions.GetArrayLength() >= 2);
}
[Fact]
public async Task LegacyWorkspaceProducesDeterministicOutputAsync()
{
var fixturePath = TestPaths.ResolveFixture("lang", "ruby", "legacy-app");
var goldenPath = Path.Combine(fixturePath, "expected.json");
var analyzers = new ILanguageAnalyzer[] { new RubyLanguageAnalyzer() };
await LanguageAnalyzerTestHarness.AssertDeterministicAsync(
fixturePath,
goldenPath,
analyzers,
TestContext.Current.CancellationToken);
}
[Fact]
public async Task LegacyWorkspaceDetectsCapabilitiesWithoutBundlerAsync()
{
var fixturePath = TestPaths.ResolveFixture("lang", "ruby", "legacy-app");
var store = new ScanAnalysisStore();
var analyzers = new ILanguageAnalyzer[] { new RubyLanguageAnalyzer() };
var engine = new LanguageAnalyzerEngine(analyzers);
var context = new LanguageAnalyzerContext(
fixturePath,
TimeProvider.System,
usageHints: null,
services: null,
analysisStore: store);
var result = await engine.AnalyzeAsync(context, TestContext.Current.CancellationToken);
var snapshots = result.ToSnapshots();
var summary = Assert.Single(snapshots, snapshot => snapshot.Type == "ruby-observation");
// Verify capabilities are still detected from source code analysis
Assert.True(summary.Metadata.TryGetValue("ruby.observation.capability.exec", out var usesExec));
Assert.Equal("true", usesExec);
Assert.True(summary.Metadata.TryGetValue("ruby.observation.capability.net", out var usesNet));
Assert.Equal("true", usesNet);
// Verify entrypoints are detected
Assert.True(summary.Metadata.TryGetValue("ruby.observation.entrypoints", out var entrypoints));
Assert.NotNull(entrypoints);
Assert.True(int.Parse(entrypoints!) >= 1);
}
}

View File

@@ -26,6 +26,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="8.4.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="xunit.v3" Version="3.0.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.3" />