diff --git a/cuke_modeler.gemspec b/cuke_modeler.gemspec index 0fb7923..a1a9518 100644 --- a/cuke_modeler.gemspec +++ b/cuke_modeler.gemspec @@ -44,4 +44,5 @@ Gem::Specification.new do |spec| # Coveralls gem does not support any newer version than this spec.add_development_dependency 'simplecov', '<= 0.16.1' spec.add_development_dependency 'test-unit', '< 4.0.0' + spec.add_development_dependency 'pry', '~> 0.13.1' end diff --git a/lib/cuke_modeler.rb b/lib/cuke_modeler.rb index d6cbe50..7c2b963 100644 --- a/lib/cuke_modeler.rb +++ b/lib/cuke_modeler.rb @@ -15,6 +15,7 @@ module CukeModeler require 'cuke_modeler/named' require 'cuke_modeler/described' require 'cuke_modeler/stepped' +require 'cuke_modeler/fingerprint' require 'cuke_modeler/models/model' require 'cuke_modeler/models/feature_file' require 'cuke_modeler/models/directory' diff --git a/lib/cuke_modeler/fingerprint.rb b/lib/cuke_modeler/fingerprint.rb new file mode 100644 index 0000000..01de729 --- /dev/null +++ b/lib/cuke_modeler/fingerprint.rb @@ -0,0 +1,33 @@ +require 'digest/md5' + +module CukeModeler + + # NOT A PART OF THE PUBLIC API + # A mix-in module containing methods used by models that represent an element that has a name. + + module Fingerprint + + # NOTE: create a fingerprint for the given model using Digest::MD5 + def fingerprint + # NOTE: yield the result value of the block as the argument to .hexdigest + if block_given? + value = yield self + + # NOTE: The block returned nil, nothing to digest here + return nil unless value.present? + + @fingerprint = Digest::MD5.hexdigest(value) + + return @fingerprint + end + + # NOTE: memoize fingerprint for improved performance + @fingerprint ||= begin + # NOTE: no block given, .hexdigest the to_s of the model + Digest::MD5.hexdigest(to_s) + end + end + + end + +end diff --git a/lib/cuke_modeler/models/model.rb b/lib/cuke_modeler/models/model.rb index 20d306f..b1e76ec 100644 --- a/lib/cuke_modeler/models/model.rb +++ b/lib/cuke_modeler/models/model.rb @@ -6,6 +6,7 @@ class Model include Nested include Containing + include Fingerprint # Creates a new Model object and, if *source_text* is provided, diff --git a/testing/rspec/spec/integration/fingerprint_integration_spec.rb b/testing/rspec/spec/integration/fingerprint_integration_spec.rb new file mode 100644 index 0000000..6655ab6 --- /dev/null +++ b/testing/rspec/spec/integration/fingerprint_integration_spec.rb @@ -0,0 +1,94 @@ +require "#{File.dirname(__FILE__)}/../spec_helper" +require 'benchmark' + +describe 'Fingerprint, Integration' do + + describe '.fingerprint' do + + let(:model) { CukeModeler::Step.new } + + before do + model.keyword = 'Given' + model.text = 'I am testing the fingerprint' + end + + describe 'getting a fingerprint without args' do + + it 'returns the Digest::MD5 of the to_s representation' do + expect(model.fingerprint).to eq(Digest::MD5.hexdigest(model.to_s)) + end + + end + + describe 'getting a fingerprint with a block' do + + it 'returns the Digest::MD5 of the block return value' do + fingerprint = model.fingerprint do |m| + m.text + end + + expect(fingerprint).to eq(Digest::MD5.hexdigest(model.text)) + end + + end + + describe 'getting a fingerprint with a block that returns nil' do + + it 'returns nil' do + fingerprint = model.fingerprint do |m| + nil + end + + expect(fingerprint).to eq(nil) + end + + end + + describe 'comparing fingerprints' do + + let(:maximum_viable_gherkin) do + "@a_tag + #{SCENARIO_KEYWORD}: test scenario + + Scenario + description + + #{STEP_KEYWORD} a step + | value1 | + | value2 | + #{STEP_KEYWORD} another step + \"\"\" with content type + some text + \"\"\"" + end + + let(:model_one) { CukeModeler::Scenario.new(maximum_viable_gherkin) } + let(:model_two) { CukeModeler::Scenario.new(maximum_viable_gherkin) } + let(:model_three) { CukeModeler::Scenario.new } + + it 'should result in the same fingerprint when classes exactly the same' do + expect(model_one.fingerprint).to eq(model_two.fingerprint) + expect(model_one.fingerprint).not_to eq(model_three.fingerprint) + end + + it 'should recalculate the fingerprint when using a block' do + model_one_fingerprint = model_one.fingerprint + model_one_block_fingerprint = model_one.fingerprint { |m| m.name } + + expect(model_one_fingerprint).not_to eq(model_one_block_fingerprint) + expect(model_one.fingerprint).to eq(model_one_block_fingerprint) + end + + it 'is more performant than ==' do + equals_time = Benchmark.realtime { model_one == model_two } + fingerprint_time = Benchmark.realtime { model_one.fingerprint == model_two.fingerprint } + + expect(equals_time > fingerprint_time).to eq(true) + end + + end + + end + +end + diff --git a/testing/rspec/spec/spec_helper.rb b/testing/rspec/spec/spec_helper.rb index 97a2414..36f99e5 100644 --- a/testing/rspec/spec/spec_helper.rb +++ b/testing/rspec/spec/spec_helper.rb @@ -24,6 +24,8 @@ require_relative '../../helper_methods' require 'rubygems/mock_gem_ui' +require 'digest/md5' +require 'pry' # Use a random dialect for testing in order to avoid hard coded language assumptions in the diff --git a/testing/rspec/spec/unit/fingerprint_unit_spec.rb b/testing/rspec/spec/unit/fingerprint_unit_spec.rb new file mode 100644 index 0000000..76aee57 --- /dev/null +++ b/testing/rspec/spec/unit/fingerprint_unit_spec.rb @@ -0,0 +1,12 @@ +require "#{File.dirname(__FILE__)}/../spec_helper" + +describe 'Fingerprint, Unit', unit_test: true do + + let(:nodule) { CukeModeler::Fingerprint } + let(:model) { Object.new.extend(nodule) } + + + it 'has a fingerprint' do + expect(model).to respond_to(:fingerprint) + end +end