diff --git a/Gemfile b/Gemfile index c2bc80e179..8bcffa5cdd 100644 --- a/Gemfile +++ b/Gemfile @@ -1,4 +1,13 @@ source 'https://rubygems.org' +ruby '2.2.5' + gem 'minitest' gem 'rubocop', '0.36.0' +gem 'simplecov' +gem 'rake' + +# These are not strictly necessary, but I use them to automatically run the +# tests whenever I edit a file. +gem 'guard' +gem 'guard-shell' diff --git a/Guardfile b/Guardfile new file mode 100644 index 0000000000..6b169849d1 --- /dev/null +++ b/Guardfile @@ -0,0 +1,39 @@ +# A sample Guardfile +# More info at https://github.com/guard/guard#readme + +## Uncomment and set this to only include directories you want to watch +# directories %w(app lib config test spec features) \ +# .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")} + +## Note: if you are using the `directories` clause above and you are not +## watching the project directory ('.'), then you will want to move +## the Guardfile to a watched dir and symlink it back, e.g. +# +# $ mkdir config +# $ mv Guardfile config/ +# $ ln -s config/Guardfile . +# +# and, you'll have to watch "config/Guardfile" instead of "Guardfile" + +interactor :off + +# Add files and commands to this file, like the example: +# watch(%r{file/path}) { `command(s)` } +# +guard :shell do + watch(/(lib|tests\/)(.*)\.rb$/) do |m| + file = m[0] + puts "File changed: #{file}" + test_file = file[/_test\.rb/] ? file : file.sub(/\.rb/, '_test.rb') + commands = + "bundle exec rake test", + "bundle exec rubocop -D #{file}" + generator = + "bin/generate isogram", + "git --no-pager diff exercises/isogram/isogram_test.rb" + command = commands.join(' && ') + p command + system(command) + system(generator.join(' && ')) + end +end diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000000..d93b680454 --- /dev/null +++ b/Rakefile @@ -0,0 +1,6 @@ +require 'rake' +require 'rake/testtask' + +Rake::TestTask.new do |task| + task.pattern = 'tests/*_test.rb' +end diff --git a/bin/executable-tests-check b/bin/executable-tests-check index 490a58a2e2..771f9d0331 100755 --- a/bin/executable-tests-check +++ b/bin/executable-tests-check @@ -3,7 +3,7 @@ require 'minitest/autorun' # Assume that this file lives in #{base}/bin base = File.join(__dir__,'..') -files = Dir.glob("#{base}/**/*test.rb") + Dir.glob("#{base}/bin/*") +files = Dir.glob("#{base}/exercises/**/*test.rb") + Dir.glob("#{base}/bin/*") files.each do |file| describe file do diff --git a/exercises/isogram/.version b/exercises/isogram/.version index d00491fd7e..d8263ee986 100644 --- a/exercises/isogram/.version +++ b/exercises/isogram/.version @@ -1 +1 @@ -1 +2 \ No newline at end of file diff --git a/exercises/isogram/example.rb b/exercises/isogram/example.rb index e2b9e3d440..fac7fb8862 100644 --- a/exercises/isogram/example.rb +++ b/exercises/isogram/example.rb @@ -1,9 +1,9 @@ module BookKeeping - VERSION = 1 + VERSION = 2 end class Isogram - def self.is_isogram?(str) + def self.isogram?(str) letters = str.downcase.gsub(/[[:punct:]]| /, '').chars letters == letters.uniq end diff --git a/exercises/isogram/example.tt b/exercises/isogram/example.tt index acdd7a80d7..9b58e2c8c3 100644 --- a/exercises/isogram/example.tt +++ b/exercises/isogram/example.tt @@ -5,16 +5,10 @@ require 'minitest/autorun' require_relative 'isogram' # Common test data version: <%= sha1 %> -class IsogramTest < Minitest::Test<% test_cases.each do |test_case| %> - def <%= test_case.name %> - <%= test_case.skip %> - string = '<%= test_case.input %>' - <%= test_case.assertion %> Isogram.is_isogram?(string) - end +class IsogramTest < Minitest::Test +<% test_cases.each_with_index do |test_case,index| %> +<%= test_case.render(index) %> + <% end %> -<%= IO.read(XRUBY_LIB + '/bookkeeping.md') %> - def test_bookkeeping - skip - assert_equal <%= version.next %>, BookKeeping::VERSION - end +<%= test_version_bookkeeping.render %> end diff --git a/exercises/isogram/isogram_test.rb b/exercises/isogram/isogram_test.rb index 238c8bd83a..f366477e11 100755 --- a/exercises/isogram/isogram_test.rb +++ b/exercises/isogram/isogram_test.rb @@ -4,66 +4,54 @@ require 'minitest/autorun' require_relative 'isogram' -# Common test data version: c1cb73f +# Common test data version: 2adfe21 class IsogramTest < Minitest::Test - def test_duplicates + def test_empty_string_has_no_duplicates # skip + string = '' + assert Isogram.isogram?(string), '"" is an isogram' + end + + def test_isogram_with_only_lower_case_characters + skip string = 'duplicates' - assert Isogram.is_isogram?(string) + assert Isogram.isogram?(string), '"duplicates" is an isogram' end - def test_eleven + def test_word_with_one_duplicated_character skip string = 'eleven' - refute Isogram.is_isogram?(string) + refute Isogram.isogram?(string), '"eleven" is NOT an isogram' end - def test_subdermatoglyphic + def test_longest_reported_english_isogram skip string = 'subdermatoglyphic' - assert Isogram.is_isogram?(string) + assert Isogram.isogram?(string), '"subdermatoglyphic" is an isogram' end - def test_alphabet + def test_word_with_duplicated_character_in_mixed_case skip string = 'Alphabet' - refute Isogram.is_isogram?(string) + refute Isogram.isogram?(string), '"Alphabet" is NOT an isogram' end - def test_thumbscrew_japingly + def test_hypothetical_isogrammic_word_with_hyphen skip string = 'thumbscrew-japingly' - assert Isogram.is_isogram?(string) + assert Isogram.isogram?(string), '"thumbscrew-japingly" is an isogram' end - def test_hjelmqvist_gryb_zock_pfund_wax + def test_isogram_with_duplicated_non_letter_character skip string = 'Hjelmqvist-Gryb-Zock-Pfund-Wax' - assert Isogram.is_isogram?(string) - end - - def test_heizölrückstoßabdämpfung - skip - string = 'Heizölrückstoßabdämpfung' - assert Isogram.is_isogram?(string) + assert Isogram.isogram?(string), '"Hjelmqvist-Gryb-Zock-Pfund-Wax" is an isogram' end - def test_the_quick_brown_fox - skip - string = 'the quick brown fox' - refute Isogram.is_isogram?(string) - end - - def test_emily_jung_schwartzkopf + def test_made_up_name_that_is_an_isogram skip string = 'Emily Jung Schwartzkopf' - assert Isogram.is_isogram?(string) - end - - def test_éléphant - skip - string = 'éléphant' - refute Isogram.is_isogram?(string) + assert Isogram.isogram?(string), '"Emily Jung Schwartzkopf" is an isogram' end # Problems in exercism evolve over time, as we find better ways to ask @@ -85,6 +73,6 @@ def test_éléphant def test_bookkeeping skip - assert_equal 1, BookKeeping::VERSION + assert_equal 2, BookKeeping::VERSION end end diff --git a/lib/exercise_testcase.rb b/lib/exercise_testcase.rb new file mode 100644 index 0000000000..c35ee47829 --- /dev/null +++ b/lib/exercise_testcase.rb @@ -0,0 +1,60 @@ +class ExerciseTestCase + attr_reader :canonical_data + + def initialize(canonical_data) + @canonical_data = OpenStruct.new(canonical_data) + end + + def render(index = -1) + @index = index + [comment, *full_method].compact.join("\n") + "\n" + end + + def full_method + indent( [ + method_definition, + *method_body, + method_end + ], 2) + end + + def comment + nil + end + + def description + canonical_data.description || '' + end + + def method_definition + "def #{test_name}" + end + + def method_body + indent( [skip, *workload], 2) + end + + def method_end + "end" + end + + def skip + @index.zero? ? '# skip' : 'skip' + end + + def test_name + "test_#{description_as_test_name}" + end + + def description_as_test_name + description.downcase.tr_s(' -','_') + end + + def workload + "assert #{@canonical_data.expected.inspect}, Subject.method #{@canonical_data.input.inspect}" + end + + def indent(lines, count) + lines.compact.map { |line| ' ' * count + line } + end +end diff --git a/lib/exercise_testcases.rb b/lib/exercise_testcases.rb new file mode 100644 index 0000000000..9653f89720 --- /dev/null +++ b/lib/exercise_testcases.rb @@ -0,0 +1,20 @@ +class ExerciseTestCases + attr_reader :cases_key + def initialize(json_data) + @data = json_data + @cases_key = 'cases' + end + + def case_classname + classname = self.class.to_s.sub(/Cases$/,'Case') + Object.const_get(classname) + end + + def parsed_json_cases + JSON.parse(@data)[cases_key] + end + + def to_a + parsed_json_cases.map { |test_case| case_classname.new(test_case) } + end +end diff --git a/lib/generator.rb b/lib/generator.rb index 48360e56cd..2ad0292f82 100644 --- a/lib/generator.rb +++ b/lib/generator.rb @@ -4,6 +4,9 @@ require 'json' require 'ostruct' +require_relative 'exercise_testcases' +require_relative 'exercise_testcase' + class Generator METADATA_REPOSITORY = 'x-common'.freeze @@ -14,7 +17,6 @@ def initialize(name, cases) end def metadata_dir - # rubocop:disable Metrics/LineLength File.expand_path(File.join('..', '..', '..', METADATA_REPOSITORY, 'exercises', name), __FILE__) end @@ -35,7 +37,22 @@ def sha1 end def test_cases - cases.call(data) + cases.new(data).to_a + end + + class BookKeeping < ExerciseTestCase + def comment + IO.read(XRUBY_LIB + '/bookkeeping.md') + end + + def workload + 'assert_equal 2, BookKeeping::VERSION' + end + end + + def test_version_bookkeeping + BookKeeping.new('description' => 'bookkeeping') + # [comment, "\n", bla.full_method].join end def metadata_repository_missing_message @@ -51,20 +68,21 @@ def metadata_repository_missing_message def generate check_metadata_repository_exists generate_test_file - increment_version - increment_version_in_example + # increment_version + # increment_version_in_example end def check_metadata_repository_exists unless File.directory?(metadata_dir) STDERR.puts metadata_repository_missing_message - fail Errno::ENOENT.new(metadata_dir) + raise Errno::ENOENT, metadata_dir end end def generate_test_file File.open(path_to("#{name.gsub(/[ -]/, '_')}_test.rb"), 'w') do |f| - f.write ERB.new(File.read(path_to('example.tt'))).result binding + template = File.read(path_to('example.tt')) + f.write ERB.new(template, nil, '<>').result binding end end diff --git a/lib/isogram_cases.rb b/lib/isogram_cases.rb index 4e042ef693..7f08e5dd30 100644 --- a/lib/isogram_cases.rb +++ b/lib/isogram_cases.rb @@ -1,24 +1,26 @@ -class IsogramCase < OpenStruct +require_relative 'exercise_testcases' +require_relative 'exercise_testcase' - def name - format('test_%s', description) - end +class IsogramCases < ExerciseTestCases +end - def description - input.downcase.gsub(/[ -]/,'_') +class IsogramCase < ExerciseTestCase + def workload + [ + "string = '#{canonical_data.input}'", + "#{assertion} Isogram.isogram?(string), '#{failure_message}'" + ] end - def assertion - expected ? 'assert' : 'refute' + def failure_message + "#{canonical_data.input.inspect} #{is_or_isnt} an isogram" end - def skip - index.zero? ? '# skip' : 'skip' + def is_or_isnt + canonical_data.expected ? 'is' : 'is NOT' end -end -IsogramCases = proc do |data| - JSON.parse(data)['cases'].map.with_index do |row, i| - IsogramCase.new(row.merge('index' => i)) + def assertion + canonical_data.expected ? 'assert' : 'refute' end end diff --git a/tests/exercise_testcase_test.rb b/tests/exercise_testcase_test.rb new file mode 100644 index 0000000000..536075e52e --- /dev/null +++ b/tests/exercise_testcase_test.rb @@ -0,0 +1,42 @@ +require_relative 'test_helper' +require 'exercise_testcase' + +class ExerciseTestCaseTest < Minitest::Test + def test_can_be_created + subject = ExerciseTestCase.new nil + assert subject.instance_of? ExerciseTestCase + end + + def test_render + subject = ExerciseTestCase.new nil + expected = " def test_\n skip\n assert nil, Subject.method nil\n end\n" + assert_equal expected, subject.render + end + + def dont_test_can_get_data + data = { 'key' => 'value' } + expected = OpenStruct.new(data) + subject = ExerciseTestCase.new(data) + assert_equal expected, subject.canonical_data + end + + def dont_test_test_name_from_description + data = { 'description' => 'from_description' } + subject = ExerciseTestCase.new(data) + assert_equal 'test_from_description', subject.test_name + end + + def dont_test_test_name_from_description_replaces_spaces + data = { 'description' => 'from description with spaces' } + subject = ExerciseTestCase.new(data) + assert_equal 'test_from_description_with_spaces', subject.test_name + end + + def dont_test_test_name_from_description_replaces_hyphens + data = { 'description' => 'from-description-with-hyphens' } + subject = ExerciseTestCase.new(data) + assert_equal 'test_from_description_with_hyphens', subject.test_name + end + + +end diff --git a/tests/exercise_testcases_test.rb b/tests/exercise_testcases_test.rb new file mode 100644 index 0000000000..f75cbd2f51 --- /dev/null +++ b/tests/exercise_testcases_test.rb @@ -0,0 +1,14 @@ +require_relative 'test_helper' +require 'exercise_testcases' +require 'exercise_testcase' +require 'json' + +class ExerciseTestCasesTest < Minitest::Test + def test_initialistion + some_json = JSON.generate(cases: [{ 'description' => 'first' },{ description: 'second' }]) + subject = ExerciseTestCases.new(some_json) + result = subject.to_a + assert_equal 2, result.size + assert_equal ExerciseTestCase, result.first.class + end +end diff --git a/tests/isogram_cases_test.rb b/tests/isogram_cases_test.rb new file mode 100644 index 0000000000..cdb9a1f45f --- /dev/null +++ b/tests/isogram_cases_test.rb @@ -0,0 +1,17 @@ +require_relative 'test_helper' +require 'isogram_cases' + +class IsogramCaseTest < Minitest::Test + def test_can_create_a_whole_test + data = { description: 'isogram is an isogram', input: 'isogram', expected: true } + subject = IsogramCase.new(data) + expected = <