spec/support/scenario_snapshot.rb
Rama develop · réplica de lectura del repositorio original.
# The snapshot of a scenario: the `compose.yaml`, `.env` and `config.yaml`
# that HELIOS produced for it. Every family under spec/scenarios/ starts
# somewhere else and ends the same way, by holding its output against the
# files recorded in `snapshot/`.
#
# UPDATE_SNAPSHOTS=1 bin/rspec spec/scenarios
#
# rewrites them instead of comparing. The recording runs inside the example,
# so there is no second implementation of the pipeline to keep in step: a
# snapshot holds exactly what the spec ran.
module ScenarioSnapshot
ROOT = Rails.root.join('spec/scenarios')
# A scenario is round-tripped when it carries a `snapshot/`. The other
# directories are input alone: their donor stacks are a corpus that the
# parser and compatibility specs read, and no stack of ours comes out of
# them (see spec/scenarios/import/minimal, real_world/user2, user6).
def self.names(family)
root = ROOT.join(family)
Pathname.glob(root.join('**/snapshot'))
.map { |path| path.parent.relative_path_from(root).to_s }
.sort
end
def self.path(family, name)
ROOT.join(family, name)
end
def self.update?
ENV['UPDATE_SNAPSHOTS'].present?
end
# Call after the export wrote the stack.
def verify_snapshot!(snapshot_path)
record_snapshot!(snapshot_path) if ScenarioSnapshot.update?
# config.yaml is compared parsed, the stack files byte for byte. A
# recorded config.yaml carries its nested keys sorted (see below), which
# the exporter has no reason to reproduce.
aggregate_failures do
expect_same_config(snapshot_path.join('helios/config.yaml'))
expect_same_text(Compose.path, snapshot_path.join('compose.yaml'))
expect_same_text(Env.path, snapshot_path.join('.env'))
# A snapshot can match and still be unusable: `docker compose` refuses a
# project whose `depends_on` names a service the export left out.
expect(dangling_service_dependencies(Compose.path)).to be_empty
end
end
private
def expect_same_config(recorded)
expect(load_config(Configuration.path)).to eq(load_config(recorded))
end
def expect_same_text(produced, recorded)
expect(File.read(produced)).to eq(File.read(recorded))
end
def record_snapshot!(snapshot_path)
config_path = snapshot_path.join('helios/config.yaml')
FileUtils.mkdir_p(config_path.dirname)
# Keep the root-level key order the exporter chose, but sort the nested
# keys so a reshuffle inside a section leaves the diff alone.
sorted = load_config(Configuration.path).transform_values { |value| deep_sort_keys(value) }
File.write(config_path, Configuration.dump(sorted))
FileUtils.cp(Compose.path, snapshot_path.join('compose.yaml'))
FileUtils.cp(Env.path, snapshot_path.join('.env'))
end
def load_config(path)
YAML.safe_load_file(path, permitted_classes: [Date])
end
def deep_sort_keys(object)
case object
when Hash then object.sort.to_h.transform_values { |value| deep_sort_keys(value) }
when Array then object.map { |element| deep_sort_keys(element) }
else object
end
end
end
RSpec.configure do |config|
config.include ScenarioSnapshot, file_path: %r{spec/scenarios/}
end