From 0ae263c5ed6c0a8061864362f90ebd9607e8f9e7 Mon Sep 17 00:00:00 2001 From: Tim Smith Date: Sat, 27 Jun 2026 22:48:39 -0700 Subject: [PATCH 1/2] Add unit specs for Util and DoNothingMatcher Close two real unit-test gaps: - ChefSpec::Util (underscore/camelize/truncate) had no spec at all; add one covering the transformations and the truncate boundary/default. - The DoNothingMatcher "spec" was named do_nothing_matcher.rb (missing the _spec suffix) so RSpec never loaded it, and its body was just `pending`. Replace it with a real do_nothing_matcher_spec.rb exercising matches?, description, and both failure messages. Signed-off-by: Tim Smith --- spec/unit/matchers/do_nothing_matcher.rb | 5 -- spec/unit/matchers/do_nothing_matcher_spec.rb | 54 +++++++++++++++++++ spec/unit/util_spec.rb | 45 ++++++++++++++++ 3 files changed, 99 insertions(+), 5 deletions(-) delete mode 100644 spec/unit/matchers/do_nothing_matcher.rb create mode 100644 spec/unit/matchers/do_nothing_matcher_spec.rb create mode 100644 spec/unit/util_spec.rb diff --git a/spec/unit/matchers/do_nothing_matcher.rb b/spec/unit/matchers/do_nothing_matcher.rb deleted file mode 100644 index 34d49ddf..00000000 --- a/spec/unit/matchers/do_nothing_matcher.rb +++ /dev/null @@ -1,5 +0,0 @@ -require "spec_helper" - -describe ChefSpec::Matchers::DoNothingMatcher do - pending -end diff --git a/spec/unit/matchers/do_nothing_matcher_spec.rb b/spec/unit/matchers/do_nothing_matcher_spec.rb new file mode 100644 index 00000000..f2c1f99e --- /dev/null +++ b/spec/unit/matchers/do_nothing_matcher_spec.rb @@ -0,0 +1,54 @@ +require "spec_helper" + +describe ChefSpec::Matchers::DoNothingMatcher do + subject { described_class.new } + let(:resource) { Chef::Resource::Execute.new("run my thing") } + + describe "#matches?" do + it "does not match when the resource is nil" do + expect(subject.matches?(nil)).to be false + end + + it "matches a resource that performed no actions" do + allow(resource).to receive(:performed_actions).and_return([]) + expect(subject.matches?(resource)).to be true + end + + it "matches a resource that only performed :nothing" do + allow(resource).to receive(:performed_actions).and_return([:nothing]) + expect(subject.matches?(resource)).to be true + end + + it "does not match a resource that performed a real action" do + allow(resource).to receive(:performed_actions).and_return([:run]) + expect(subject.matches?(resource)).to be false + end + end + + describe "#description" do + it "has the right value" do + expect(subject.description).to eq("do nothing") + end + end + + describe "#failure_message" do + it "lists the actions that were performed" do + allow(resource).to receive(:performed_actions).and_return([:run]) + subject.matches?(resource) + expect(subject.failure_message).to include("to do nothing", ":run") + end + + it "explains when the resource was nil" do + subject.matches?(nil) + expect(subject.failure_message).to include("you gave me was nil") + end + end + + describe "#failure_message_when_negated" do + it "explains that no actions were performed" do + allow(resource).to receive(:performed_actions).and_return([]) + subject.matches?(resource) + expect(subject.failure_message_when_negated).to include("to do something") + end + end +end diff --git a/spec/unit/util_spec.rb b/spec/unit/util_spec.rb new file mode 100644 index 00000000..ce92d93d --- /dev/null +++ b/spec/unit/util_spec.rb @@ -0,0 +1,45 @@ +require "spec_helper" + +describe ChefSpec::Util do + describe "#underscore" do + it "converts CamelCase to snake_case" do + expect(described_class.underscore("CamelCase")).to eq("camel_case") + end + + it "converts namespaced constants to slash-separated paths" do + expect(described_class.underscore("ChefSpec::SoloRunner")).to eq("chef_spec/solo_runner") + end + + it "handles runs of capitals (acronyms)" do + expect(described_class.underscore("HTTPRequest")).to eq("http_request") + end + + it "converts dashes to underscores" do + expect(described_class.underscore("my-cookbook")).to eq("my_cookbook") + end + end + + describe "#camelize" do + it "converts snake_case to CamelCase" do + expect(described_class.camelize("camel_case")).to eq("CamelCase") + end + + it "leaves a single word capitalized" do + expect(described_class.camelize("runner")).to eq("Runner") + end + end + + describe "#truncate" do + it "returns the string unchanged when it is within the limit" do + expect(described_class.truncate("short")).to eq("short") + end + + it "truncates with a trailing ellipsis when over the limit" do + expect(described_class.truncate("#{"x" * 40}", length: 10)).to eq("xxxxxxxx...") + end + + it "defaults to a length of 30" do + expect(described_class.truncate("y" * 40)).to eq("#{"y" * 28}...") + end + end +end From edc958fcc91572ad649daa03fef694fb7f3859c2 Mon Sep 17 00:00:00 2001 From: Tim Smith Date: Sat, 27 Jun 2026 22:53:14 -0700 Subject: [PATCH 2/2] Add unit specs for Normalize, FileCachePathProxy, and Coverage Expand unit coverage of the pure, unit-appropriate code: - ChefSpec::Normalize#resource_name: declared_type/resource_name/string fallbacks and dash-to-underscore normalization. - ChefSpec::FileCachePathProxy: singleton identity and that it exposes a real temp directory. - ChefSpec::Coverage::ResourceWrapper: source_file/source_line parsing (with and without a source line), touch!/touched? state, and to_json. Signed-off-by: Tim Smith --- spec/unit/coverage_spec.rb | 58 +++++++++++++++++++++++++ spec/unit/file_cache_path_proxy_spec.rb | 14 ++++++ spec/unit/mixins/normalize_spec.rb | 30 +++++++++++++ 3 files changed, 102 insertions(+) create mode 100644 spec/unit/coverage_spec.rb create mode 100644 spec/unit/file_cache_path_proxy_spec.rb create mode 100644 spec/unit/mixins/normalize_spec.rb diff --git a/spec/unit/coverage_spec.rb b/spec/unit/coverage_spec.rb new file mode 100644 index 00000000..05c3ff95 --- /dev/null +++ b/spec/unit/coverage_spec.rb @@ -0,0 +1,58 @@ +require "spec_helper" + +describe ChefSpec::Coverage::ResourceWrapper do + let(:resource) do + double("resource", to_s: "template[/etc/foo]", source_line: "/cookbooks/web/recipes/default.rb:12") + end + subject(:wrapper) { described_class.new(resource) } + + describe "#to_s" do + it "delegates to the wrapped resource" do + expect(wrapper.to_s).to eq("template[/etc/foo]") + end + end + + describe "#source_file" do + it "extracts the cookbook-relative file from the source line" do + expect(wrapper.source_file).to eq("web/recipes/default.rb") + end + + it "returns 'Unknown' when the resource has no source line" do + allow(resource).to receive(:source_line).and_return(nil) + expect(wrapper.source_file).to eq("Unknown") + end + end + + describe "#source_line" do + it "extracts the line number as an integer" do + expect(wrapper.source_line).to eq(12) + end + + it "returns 'Unknown' when the resource has no source line" do + allow(resource).to receive(:source_line).and_return(nil) + expect(wrapper.source_line).to eq("Unknown") + end + end + + describe "#touch! and #touched?" do + it "is untouched by default" do + expect(wrapper.touched?).to be(false) + end + + it "is touched after #touch!" do + wrapper.touch! + expect(wrapper.touched?).to be(true) + end + end + + describe "#to_json" do + it "serializes the resource details" do + require "json" + expect(JSON.parse(wrapper.to_json)).to include( + "resource" => "template[/etc/foo]", + "source_line" => 12, + "touched" => false + ) + end + end +end diff --git a/spec/unit/file_cache_path_proxy_spec.rb b/spec/unit/file_cache_path_proxy_spec.rb new file mode 100644 index 00000000..eddf017e --- /dev/null +++ b/spec/unit/file_cache_path_proxy_spec.rb @@ -0,0 +1,14 @@ +require "spec_helper" + +describe ChefSpec::FileCachePathProxy do + subject { described_class.instance } + + it "is a singleton" do + expect(described_class.instance).to be(described_class.instance) + end + + it "exposes a real, existing temp directory" do + expect(subject.file_cache_path).to be_a(String) + expect(Dir.exist?(subject.file_cache_path)).to be(true) + end +end diff --git a/spec/unit/mixins/normalize_spec.rb b/spec/unit/mixins/normalize_spec.rb new file mode 100644 index 00000000..0c793289 --- /dev/null +++ b/spec/unit/mixins/normalize_spec.rb @@ -0,0 +1,30 @@ +require "spec_helper" + +describe ChefSpec::Normalize do + subject(:normalizer) { Class.new { include ChefSpec::Normalize }.new } + + describe "#resource_name" do + it "returns a symbol for a plain string" do + expect(normalizer.resource_name("file")).to eq(:file) + end + + it "converts dashes to underscores" do + expect(normalizer.resource_name("my-resource")).to eq(:my_resource) + end + + it "prefers declared_type when present" do + thing = double(declared_type: "yum_repository") + expect(normalizer.resource_name(thing)).to eq(:yum_repository) + end + + it "normalizes dashes in a declared_type" do + thing = double(declared_type: "yum-repository") + expect(normalizer.resource_name(thing)).to eq(:yum_repository) + end + + it "falls back to resource_name when declared_type is nil" do + thing = double(declared_type: nil, resource_name: "template") + expect(normalizer.resource_name(thing)).to eq(:template) + end + end +end