From 95e8c48dd3348503a8c7db5d0498894a1b676395 Mon Sep 17 00:00:00 2001 From: eregon Date: Sun, 7 May 2017 12:04:49 +0000 Subject: Add in-tree mspec and ruby/spec * For easier modifications of ruby/spec by MRI developers. * .gitignore: track changes under spec. * spec/mspec, spec/rubyspec: add in-tree mspec and ruby/spec. These files can therefore be updated like any other file in MRI. Instructions are provided in spec/README. [Feature #13156] [ruby-core:79246] git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@58595 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- spec/mspec/.gitignore | 26 + spec/mspec/.travis.yml | 9 + spec/mspec/Gemfile | 4 + spec/mspec/Gemfile.lock | 30 + spec/mspec/LICENSE | 22 + spec/mspec/README.md | 88 ++ spec/mspec/Rakefile | 7 + spec/mspec/bin/mkspec | 7 + spec/mspec/bin/mkspec.bat | 1 + spec/mspec/bin/mspec | 7 + spec/mspec/bin/mspec-ci | 7 + spec/mspec/bin/mspec-ci.bat | 1 + spec/mspec/bin/mspec-run | 7 + spec/mspec/bin/mspec-run.bat | 1 + spec/mspec/bin/mspec-tag | 7 + spec/mspec/bin/mspec-tag.bat | 1 + spec/mspec/bin/mspec.bat | 1 + spec/mspec/lib/mspec.rb | 20 + spec/mspec/lib/mspec/commands/mkspec.rb | 155 +++ spec/mspec/lib/mspec/commands/mspec-ci.rb | 79 ++ spec/mspec/lib/mspec/commands/mspec-run.rb | 87 ++ spec/mspec/lib/mspec/commands/mspec-tag.rb | 133 ++ spec/mspec/lib/mspec/commands/mspec.rb | 163 +++ spec/mspec/lib/mspec/expectations.rb | 2 + spec/mspec/lib/mspec/expectations/expectations.rb | 21 + spec/mspec/lib/mspec/expectations/should.rb | 29 + spec/mspec/lib/mspec/guards.rb | 12 + spec/mspec/lib/mspec/guards/block_device.rb | 18 + spec/mspec/lib/mspec/guards/bug.rb | 30 + spec/mspec/lib/mspec/guards/conflict.rb | 19 + spec/mspec/lib/mspec/guards/endian.rb | 27 + spec/mspec/lib/mspec/guards/feature.rb | 43 + spec/mspec/lib/mspec/guards/guard.rb | 118 ++ spec/mspec/lib/mspec/guards/platform.rb | 78 ++ spec/mspec/lib/mspec/guards/quarantine.rb | 13 + spec/mspec/lib/mspec/guards/superuser.rb | 17 + spec/mspec/lib/mspec/guards/support.rb | 16 + spec/mspec/lib/mspec/guards/version.rb | 39 + spec/mspec/lib/mspec/helpers.rb | 12 + spec/mspec/lib/mspec/helpers/argf.rb | 37 + spec/mspec/lib/mspec/helpers/argv.rb | 46 + spec/mspec/lib/mspec/helpers/datetime.rb | 51 + spec/mspec/lib/mspec/helpers/fixture.rb | 26 + spec/mspec/lib/mspec/helpers/flunk.rb | 5 + spec/mspec/lib/mspec/helpers/fs.rb | 62 + spec/mspec/lib/mspec/helpers/io.rb | 113 ++ spec/mspec/lib/mspec/helpers/mock_to_path.rb | 8 + spec/mspec/lib/mspec/helpers/numeric.rb | 72 ++ spec/mspec/lib/mspec/helpers/ruby_exe.rb | 178 +++ spec/mspec/lib/mspec/helpers/scratch.rb | 17 + spec/mspec/lib/mspec/helpers/tmp.rb | 45 + spec/mspec/lib/mspec/matchers.rb | 35 + spec/mspec/lib/mspec/matchers/base.rb | 95 ++ spec/mspec/lib/mspec/matchers/be_an_instance_of.rb | 26 + spec/mspec/lib/mspec/matchers/be_ancestor_of.rb | 24 + spec/mspec/lib/mspec/matchers/be_close.rb | 27 + spec/mspec/lib/mspec/matchers/be_computed_by.rb | 37 + spec/mspec/lib/mspec/matchers/be_empty.rb | 20 + spec/mspec/lib/mspec/matchers/be_false.rb | 20 + spec/mspec/lib/mspec/matchers/be_kind_of.rb | 24 + spec/mspec/lib/mspec/matchers/be_nan.rb | 20 + spec/mspec/lib/mspec/matchers/be_nil.rb | 20 + spec/mspec/lib/mspec/matchers/be_true.rb | 20 + spec/mspec/lib/mspec/matchers/be_true_or_false.rb | 20 + spec/mspec/lib/mspec/matchers/block_caller.rb | 35 + spec/mspec/lib/mspec/matchers/complain.rb | 56 + spec/mspec/lib/mspec/matchers/eql.rb | 26 + spec/mspec/lib/mspec/matchers/equal.rb | 26 + spec/mspec/lib/mspec/matchers/equal_element.rb | 78 ++ .../lib/mspec/matchers/have_class_variable.rb | 12 + spec/mspec/lib/mspec/matchers/have_constant.rb | 12 + .../lib/mspec/matchers/have_instance_method.rb | 24 + .../lib/mspec/matchers/have_instance_variable.rb | 12 + spec/mspec/lib/mspec/matchers/have_method.rb | 24 + .../mspec/matchers/have_private_instance_method.rb | 24 + .../lib/mspec/matchers/have_private_method.rb | 24 + .../matchers/have_protected_instance_method.rb | 24 + .../mspec/matchers/have_public_instance_method.rb | 24 + .../lib/mspec/matchers/have_singleton_method.rb | 24 + spec/mspec/lib/mspec/matchers/include.rb | 32 + spec/mspec/lib/mspec/matchers/infinity.rb | 28 + spec/mspec/lib/mspec/matchers/match_yaml.rb | 47 + spec/mspec/lib/mspec/matchers/method.rb | 10 + spec/mspec/lib/mspec/matchers/output.rb | 67 + spec/mspec/lib/mspec/matchers/output_to_fd.rb | 71 ++ spec/mspec/lib/mspec/matchers/raise_error.rb | 79 ++ spec/mspec/lib/mspec/matchers/respond_to.rb | 24 + spec/mspec/lib/mspec/matchers/signed_zero.rb | 28 + spec/mspec/lib/mspec/matchers/variable.rb | 24 + spec/mspec/lib/mspec/mocks.rb | 3 + spec/mspec/lib/mspec/mocks/mock.rb | 197 +++ spec/mspec/lib/mspec/mocks/object.rb | 28 + spec/mspec/lib/mspec/mocks/proxy.rb | 186 +++ spec/mspec/lib/mspec/runner.rb | 12 + spec/mspec/lib/mspec/runner/actions.rb | 6 + spec/mspec/lib/mspec/runner/actions/filter.rb | 40 + spec/mspec/lib/mspec/runner/actions/leakchecker.rb | 301 +++++ spec/mspec/lib/mspec/runner/actions/tag.rb | 133 ++ spec/mspec/lib/mspec/runner/actions/taglist.rb | 56 + spec/mspec/lib/mspec/runner/actions/tagpurge.rb | 56 + spec/mspec/lib/mspec/runner/actions/tally.rb | 133 ++ spec/mspec/lib/mspec/runner/actions/timer.rb | 22 + spec/mspec/lib/mspec/runner/context.rb | 239 ++++ spec/mspec/lib/mspec/runner/evaluate.rb | 54 + spec/mspec/lib/mspec/runner/example.rb | 34 + spec/mspec/lib/mspec/runner/exception.rb | 43 + spec/mspec/lib/mspec/runner/filters.rb | 4 + spec/mspec/lib/mspec/runner/filters/match.rb | 18 + spec/mspec/lib/mspec/runner/filters/profile.rb | 54 + spec/mspec/lib/mspec/runner/filters/regexp.rb | 7 + spec/mspec/lib/mspec/runner/filters/tag.rb | 29 + spec/mspec/lib/mspec/runner/formatters.rb | 12 + spec/mspec/lib/mspec/runner/formatters/describe.rb | 24 + spec/mspec/lib/mspec/runner/formatters/dotted.rb | 117 ++ spec/mspec/lib/mspec/runner/formatters/file.rb | 19 + spec/mspec/lib/mspec/runner/formatters/html.rb | 81 ++ spec/mspec/lib/mspec/runner/formatters/junit.rb | 89 ++ spec/mspec/lib/mspec/runner/formatters/method.rb | 93 ++ spec/mspec/lib/mspec/runner/formatters/multi.rb | 36 + spec/mspec/lib/mspec/runner/formatters/profile.rb | 70 ++ spec/mspec/lib/mspec/runner/formatters/specdoc.rb | 41 + spec/mspec/lib/mspec/runner/formatters/spinner.rb | 117 ++ spec/mspec/lib/mspec/runner/formatters/summary.rb | 11 + spec/mspec/lib/mspec/runner/formatters/unit.rb | 21 + spec/mspec/lib/mspec/runner/formatters/yaml.rb | 42 + spec/mspec/lib/mspec/runner/mspec.rb | 391 ++++++ spec/mspec/lib/mspec/runner/object.rb | 28 + spec/mspec/lib/mspec/runner/shared.rb | 12 + spec/mspec/lib/mspec/runner/tag.rb | 38 + spec/mspec/lib/mspec/utils/deprecate.rb | 6 + spec/mspec/lib/mspec/utils/name_map.rb | 128 ++ spec/mspec/lib/mspec/utils/options.rb | 489 ++++++++ spec/mspec/lib/mspec/utils/ruby_name.rb | 8 + spec/mspec/lib/mspec/utils/script.rb | 267 ++++ spec/mspec/lib/mspec/utils/version.rb | 52 + spec/mspec/lib/mspec/utils/warnings.rb | 32 + spec/mspec/lib/mspec/version.rb | 5 + spec/mspec/mspec.gemspec | 40 + spec/mspec/spec/commands/fixtures/four.txt | 0 .../spec/commands/fixtures/level2/three_spec.rb | 0 spec/mspec/spec/commands/fixtures/one_spec.rb | 0 spec/mspec/spec/commands/fixtures/three.rb | 0 spec/mspec/spec/commands/fixtures/two_spec.rb | 0 spec/mspec/spec/commands/mkspec_spec.rb | 363 ++++++ spec/mspec/spec/commands/mspec_ci_spec.rb | 155 +++ spec/mspec/spec/commands/mspec_run_spec.rb | 185 +++ spec/mspec/spec/commands/mspec_spec.rb | 215 ++++ spec/mspec/spec/commands/mspec_tag_spec.rb | 419 +++++++ spec/mspec/spec/expectations/expectations_spec.rb | 29 + spec/mspec/spec/expectations/should.rb | 72 ++ spec/mspec/spec/expectations/should_spec.rb | 61 + spec/mspec/spec/fixtures/a_spec.rb | 15 + spec/mspec/spec/fixtures/b_spec.rb | 7 + spec/mspec/spec/fixtures/config.mspec | 10 + spec/mspec/spec/fixtures/my_ruby | 4 + spec/mspec/spec/fixtures/print_interpreter_spec.rb | 4 + spec/mspec/spec/fixtures/tagging_spec.rb | 16 + spec/mspec/spec/guards/block_device_spec.rb | 46 + spec/mspec/spec/guards/bug_spec.rb | 151 +++ spec/mspec/spec/guards/conflict_spec.rb | 51 + spec/mspec/spec/guards/endian_spec.rb | 55 + spec/mspec/spec/guards/feature_spec.rb | 80 ++ spec/mspec/spec/guards/guard_spec.rb | 180 +++ spec/mspec/spec/guards/platform_spec.rb | 331 +++++ spec/mspec/spec/guards/quarantine_spec.rb | 35 + spec/mspec/spec/guards/superuser_spec.rb | 35 + spec/mspec/spec/guards/support_spec.rb | 69 ++ spec/mspec/spec/guards/user_spec.rb | 20 + spec/mspec/spec/guards/version_spec.rb | 83 ++ spec/mspec/spec/helpers/argf_spec.rb | 37 + spec/mspec/spec/helpers/argv_spec.rb | 27 + spec/mspec/spec/helpers/datetime_spec.rb | 44 + spec/mspec/spec/helpers/fixture_spec.rb | 25 + spec/mspec/spec/helpers/flunk_spec.rb | 20 + spec/mspec/spec/helpers/fs_spec.rb | 182 +++ spec/mspec/spec/helpers/io_spec.rb | 174 +++ spec/mspec/spec/helpers/mock_to_path_spec.rb | 17 + spec/mspec/spec/helpers/numeric_spec.rb | 25 + spec/mspec/spec/helpers/ruby_exe_spec.rb | 220 ++++ spec/mspec/spec/helpers/scratch_spec.rb | 24 + spec/mspec/spec/helpers/tmp_spec.rb | 27 + spec/mspec/spec/integration/interpreter_spec.rb | 18 + spec/mspec/spec/integration/run_spec.rb | 52 + spec/mspec/spec/integration/tag_spec.rb | 63 + spec/mspec/spec/matchers/base_spec.rb | 225 ++++ spec/mspec/spec/matchers/be_an_instance_of_spec.rb | 50 + spec/mspec/spec/matchers/be_ancestor_of_spec.rb | 28 + spec/mspec/spec/matchers/be_close_spec.rb | 46 + spec/mspec/spec/matchers/be_computed_by_spec.rb | 42 + spec/mspec/spec/matchers/be_empty_spec.rb | 26 + spec/mspec/spec/matchers/be_false_spec.rb | 28 + spec/mspec/spec/matchers/be_kind_of_spec.rb | 31 + spec/mspec/spec/matchers/be_nan_spec.rb | 28 + spec/mspec/spec/matchers/be_nil_spec.rb | 27 + spec/mspec/spec/matchers/be_true_or_false_spec.rb | 19 + spec/mspec/spec/matchers/be_true_spec.rb | 28 + spec/mspec/spec/matchers/block_caller_spec.rb | 13 + spec/mspec/spec/matchers/complain_spec.rb | 52 + spec/mspec/spec/matchers/eql_spec.rb | 33 + spec/mspec/spec/matchers/equal_element_spec.rb | 75 ++ spec/mspec/spec/matchers/equal_spec.rb | 32 + .../spec/matchers/have_class_variable_spec.rb | 62 + spec/mspec/spec/matchers/have_constant_spec.rb | 37 + .../spec/matchers/have_instance_method_spec.rb | 53 + .../spec/matchers/have_instance_variable_spec.rb | 61 + spec/mspec/spec/matchers/have_method_spec.rb | 55 + .../matchers/have_private_instance_method_spec.rb | 57 + .../spec/matchers/have_private_method_spec.rb | 44 + .../have_protected_instance_method_spec.rb | 57 + .../matchers/have_public_instance_method_spec.rb | 53 + .../spec/matchers/have_singleton_method_spec.rb | 45 + spec/mspec/spec/matchers/include_spec.rb | 37 + spec/mspec/spec/matchers/infinity_spec.rb | 34 + spec/mspec/spec/matchers/match_yaml_spec.rb | 39 + spec/mspec/spec/matchers/output_spec.rb | 74 ++ spec/mspec/spec/matchers/output_to_fd_spec.rb | 42 + spec/mspec/spec/matchers/raise_error_spec.rb | 108 ++ spec/mspec/spec/matchers/respond_to_spec.rb | 33 + spec/mspec/spec/matchers/signed_zero_spec.rb | 32 + spec/mspec/spec/mocks/mock_spec.rb | 469 +++++++ spec/mspec/spec/mocks/proxy_spec.rb | 405 ++++++ spec/mspec/spec/runner/actions/filter_spec.rb | 84 ++ spec/mspec/spec/runner/actions/tag_spec.rb | 315 +++++ spec/mspec/spec/runner/actions/taglist_spec.rb | 152 +++ spec/mspec/spec/runner/actions/tagpurge_spec.rb | 154 +++ spec/mspec/spec/runner/actions/tally_spec.rb | 352 ++++++ spec/mspec/spec/runner/actions/timer_spec.rb | 44 + spec/mspec/spec/runner/context_spec.rb | 1041 ++++++++++++++++ spec/mspec/spec/runner/example_spec.rb | 117 ++ spec/mspec/spec/runner/exception_spec.rb | 146 +++ spec/mspec/spec/runner/filters/a.yaml | 4 + spec/mspec/spec/runner/filters/b.yaml | 11 + spec/mspec/spec/runner/filters/match_spec.rb | 34 + spec/mspec/spec/runner/filters/profile_spec.rb | 117 ++ spec/mspec/spec/runner/filters/regexp_spec.rb | 13 + spec/mspec/spec/runner/filters/tag_spec.rb | 92 ++ spec/mspec/spec/runner/formatters/describe_spec.rb | 67 + spec/mspec/spec/runner/formatters/dotted_spec.rb | 285 +++++ spec/mspec/spec/runner/formatters/file_spec.rb | 84 ++ spec/mspec/spec/runner/formatters/html_spec.rb | 217 ++++ spec/mspec/spec/runner/formatters/junit_spec.rb | 147 +++ spec/mspec/spec/runner/formatters/method_spec.rb | 178 +++ spec/mspec/spec/runner/formatters/multi_spec.rb | 68 + spec/mspec/spec/runner/formatters/specdoc_spec.rb | 106 ++ spec/mspec/spec/runner/formatters/spinner_spec.rb | 83 ++ spec/mspec/spec/runner/formatters/summary_spec.rb | 26 + spec/mspec/spec/runner/formatters/unit_spec.rb | 74 ++ spec/mspec/spec/runner/formatters/yaml_spec.rb | 125 ++ spec/mspec/spec/runner/mspec_spec.rb | 595 +++++++++ spec/mspec/spec/runner/shared_spec.rb | 88 ++ spec/mspec/spec/runner/tag_spec.rb | 123 ++ spec/mspec/spec/runner/tags.txt | 4 + spec/mspec/spec/spec_helper.rb | 54 + spec/mspec/spec/utils/deprecate_spec.rb | 17 + spec/mspec/spec/utils/name_map_spec.rb | 175 +++ spec/mspec/spec/utils/options_spec.rb | 1309 ++++++++++++++++++++ spec/mspec/spec/utils/script_spec.rb | 473 +++++++ spec/mspec/spec/utils/version_spec.rb | 45 + spec/mspec/tool/remove_old_guards.rb | 41 + 259 files changed, 20450 insertions(+) create mode 100644 spec/mspec/.gitignore create mode 100644 spec/mspec/.travis.yml create mode 100644 spec/mspec/Gemfile create mode 100644 spec/mspec/Gemfile.lock create mode 100644 spec/mspec/LICENSE create mode 100644 spec/mspec/README.md create mode 100644 spec/mspec/Rakefile create mode 100755 spec/mspec/bin/mkspec create mode 100755 spec/mspec/bin/mkspec.bat create mode 100755 spec/mspec/bin/mspec create mode 100755 spec/mspec/bin/mspec-ci create mode 100755 spec/mspec/bin/mspec-ci.bat create mode 100755 spec/mspec/bin/mspec-run create mode 100755 spec/mspec/bin/mspec-run.bat create mode 100755 spec/mspec/bin/mspec-tag create mode 100755 spec/mspec/bin/mspec-tag.bat create mode 100755 spec/mspec/bin/mspec.bat create mode 100644 spec/mspec/lib/mspec.rb create mode 100755 spec/mspec/lib/mspec/commands/mkspec.rb create mode 100644 spec/mspec/lib/mspec/commands/mspec-ci.rb create mode 100644 spec/mspec/lib/mspec/commands/mspec-run.rb create mode 100644 spec/mspec/lib/mspec/commands/mspec-tag.rb create mode 100755 spec/mspec/lib/mspec/commands/mspec.rb create mode 100644 spec/mspec/lib/mspec/expectations.rb create mode 100644 spec/mspec/lib/mspec/expectations/expectations.rb create mode 100644 spec/mspec/lib/mspec/expectations/should.rb create mode 100644 spec/mspec/lib/mspec/guards.rb create mode 100644 spec/mspec/lib/mspec/guards/block_device.rb create mode 100644 spec/mspec/lib/mspec/guards/bug.rb create mode 100644 spec/mspec/lib/mspec/guards/conflict.rb create mode 100644 spec/mspec/lib/mspec/guards/endian.rb create mode 100644 spec/mspec/lib/mspec/guards/feature.rb create mode 100644 spec/mspec/lib/mspec/guards/guard.rb create mode 100644 spec/mspec/lib/mspec/guards/platform.rb create mode 100644 spec/mspec/lib/mspec/guards/quarantine.rb create mode 100644 spec/mspec/lib/mspec/guards/superuser.rb create mode 100644 spec/mspec/lib/mspec/guards/support.rb create mode 100644 spec/mspec/lib/mspec/guards/version.rb create mode 100644 spec/mspec/lib/mspec/helpers.rb create mode 100644 spec/mspec/lib/mspec/helpers/argf.rb create mode 100644 spec/mspec/lib/mspec/helpers/argv.rb create mode 100644 spec/mspec/lib/mspec/helpers/datetime.rb create mode 100644 spec/mspec/lib/mspec/helpers/fixture.rb create mode 100644 spec/mspec/lib/mspec/helpers/flunk.rb create mode 100644 spec/mspec/lib/mspec/helpers/fs.rb create mode 100644 spec/mspec/lib/mspec/helpers/io.rb create mode 100644 spec/mspec/lib/mspec/helpers/mock_to_path.rb create mode 100644 spec/mspec/lib/mspec/helpers/numeric.rb create mode 100644 spec/mspec/lib/mspec/helpers/ruby_exe.rb create mode 100644 spec/mspec/lib/mspec/helpers/scratch.rb create mode 100644 spec/mspec/lib/mspec/helpers/tmp.rb create mode 100644 spec/mspec/lib/mspec/matchers.rb create mode 100644 spec/mspec/lib/mspec/matchers/base.rb create mode 100644 spec/mspec/lib/mspec/matchers/be_an_instance_of.rb create mode 100644 spec/mspec/lib/mspec/matchers/be_ancestor_of.rb create mode 100644 spec/mspec/lib/mspec/matchers/be_close.rb create mode 100644 spec/mspec/lib/mspec/matchers/be_computed_by.rb create mode 100644 spec/mspec/lib/mspec/matchers/be_empty.rb create mode 100644 spec/mspec/lib/mspec/matchers/be_false.rb create mode 100644 spec/mspec/lib/mspec/matchers/be_kind_of.rb create mode 100644 spec/mspec/lib/mspec/matchers/be_nan.rb create mode 100644 spec/mspec/lib/mspec/matchers/be_nil.rb create mode 100644 spec/mspec/lib/mspec/matchers/be_true.rb create mode 100644 spec/mspec/lib/mspec/matchers/be_true_or_false.rb create mode 100644 spec/mspec/lib/mspec/matchers/block_caller.rb create mode 100644 spec/mspec/lib/mspec/matchers/complain.rb create mode 100644 spec/mspec/lib/mspec/matchers/eql.rb create mode 100644 spec/mspec/lib/mspec/matchers/equal.rb create mode 100644 spec/mspec/lib/mspec/matchers/equal_element.rb create mode 100644 spec/mspec/lib/mspec/matchers/have_class_variable.rb create mode 100644 spec/mspec/lib/mspec/matchers/have_constant.rb create mode 100644 spec/mspec/lib/mspec/matchers/have_instance_method.rb create mode 100644 spec/mspec/lib/mspec/matchers/have_instance_variable.rb create mode 100644 spec/mspec/lib/mspec/matchers/have_method.rb create mode 100644 spec/mspec/lib/mspec/matchers/have_private_instance_method.rb create mode 100644 spec/mspec/lib/mspec/matchers/have_private_method.rb create mode 100644 spec/mspec/lib/mspec/matchers/have_protected_instance_method.rb create mode 100644 spec/mspec/lib/mspec/matchers/have_public_instance_method.rb create mode 100644 spec/mspec/lib/mspec/matchers/have_singleton_method.rb create mode 100644 spec/mspec/lib/mspec/matchers/include.rb create mode 100644 spec/mspec/lib/mspec/matchers/infinity.rb create mode 100644 spec/mspec/lib/mspec/matchers/match_yaml.rb create mode 100644 spec/mspec/lib/mspec/matchers/method.rb create mode 100644 spec/mspec/lib/mspec/matchers/output.rb create mode 100644 spec/mspec/lib/mspec/matchers/output_to_fd.rb create mode 100644 spec/mspec/lib/mspec/matchers/raise_error.rb create mode 100644 spec/mspec/lib/mspec/matchers/respond_to.rb create mode 100644 spec/mspec/lib/mspec/matchers/signed_zero.rb create mode 100644 spec/mspec/lib/mspec/matchers/variable.rb create mode 100644 spec/mspec/lib/mspec/mocks.rb create mode 100644 spec/mspec/lib/mspec/mocks/mock.rb create mode 100644 spec/mspec/lib/mspec/mocks/object.rb create mode 100644 spec/mspec/lib/mspec/mocks/proxy.rb create mode 100644 spec/mspec/lib/mspec/runner.rb create mode 100644 spec/mspec/lib/mspec/runner/actions.rb create mode 100644 spec/mspec/lib/mspec/runner/actions/filter.rb create mode 100644 spec/mspec/lib/mspec/runner/actions/leakchecker.rb create mode 100644 spec/mspec/lib/mspec/runner/actions/tag.rb create mode 100644 spec/mspec/lib/mspec/runner/actions/taglist.rb create mode 100644 spec/mspec/lib/mspec/runner/actions/tagpurge.rb create mode 100644 spec/mspec/lib/mspec/runner/actions/tally.rb create mode 100644 spec/mspec/lib/mspec/runner/actions/timer.rb create mode 100644 spec/mspec/lib/mspec/runner/context.rb create mode 100644 spec/mspec/lib/mspec/runner/evaluate.rb create mode 100644 spec/mspec/lib/mspec/runner/example.rb create mode 100644 spec/mspec/lib/mspec/runner/exception.rb create mode 100644 spec/mspec/lib/mspec/runner/filters.rb create mode 100644 spec/mspec/lib/mspec/runner/filters/match.rb create mode 100644 spec/mspec/lib/mspec/runner/filters/profile.rb create mode 100644 spec/mspec/lib/mspec/runner/filters/regexp.rb create mode 100644 spec/mspec/lib/mspec/runner/filters/tag.rb create mode 100644 spec/mspec/lib/mspec/runner/formatters.rb create mode 100644 spec/mspec/lib/mspec/runner/formatters/describe.rb create mode 100644 spec/mspec/lib/mspec/runner/formatters/dotted.rb create mode 100644 spec/mspec/lib/mspec/runner/formatters/file.rb create mode 100644 spec/mspec/lib/mspec/runner/formatters/html.rb create mode 100644 spec/mspec/lib/mspec/runner/formatters/junit.rb create mode 100644 spec/mspec/lib/mspec/runner/formatters/method.rb create mode 100644 spec/mspec/lib/mspec/runner/formatters/multi.rb create mode 100644 spec/mspec/lib/mspec/runner/formatters/profile.rb create mode 100644 spec/mspec/lib/mspec/runner/formatters/specdoc.rb create mode 100644 spec/mspec/lib/mspec/runner/formatters/spinner.rb create mode 100644 spec/mspec/lib/mspec/runner/formatters/summary.rb create mode 100644 spec/mspec/lib/mspec/runner/formatters/unit.rb create mode 100644 spec/mspec/lib/mspec/runner/formatters/yaml.rb create mode 100644 spec/mspec/lib/mspec/runner/mspec.rb create mode 100644 spec/mspec/lib/mspec/runner/object.rb create mode 100644 spec/mspec/lib/mspec/runner/shared.rb create mode 100644 spec/mspec/lib/mspec/runner/tag.rb create mode 100644 spec/mspec/lib/mspec/utils/deprecate.rb create mode 100644 spec/mspec/lib/mspec/utils/name_map.rb create mode 100644 spec/mspec/lib/mspec/utils/options.rb create mode 100644 spec/mspec/lib/mspec/utils/ruby_name.rb create mode 100644 spec/mspec/lib/mspec/utils/script.rb create mode 100644 spec/mspec/lib/mspec/utils/version.rb create mode 100644 spec/mspec/lib/mspec/utils/warnings.rb create mode 100644 spec/mspec/lib/mspec/version.rb create mode 100644 spec/mspec/mspec.gemspec create mode 100644 spec/mspec/spec/commands/fixtures/four.txt create mode 100644 spec/mspec/spec/commands/fixtures/level2/three_spec.rb create mode 100644 spec/mspec/spec/commands/fixtures/one_spec.rb create mode 100644 spec/mspec/spec/commands/fixtures/three.rb create mode 100644 spec/mspec/spec/commands/fixtures/two_spec.rb create mode 100644 spec/mspec/spec/commands/mkspec_spec.rb create mode 100644 spec/mspec/spec/commands/mspec_ci_spec.rb create mode 100644 spec/mspec/spec/commands/mspec_run_spec.rb create mode 100644 spec/mspec/spec/commands/mspec_spec.rb create mode 100644 spec/mspec/spec/commands/mspec_tag_spec.rb create mode 100644 spec/mspec/spec/expectations/expectations_spec.rb create mode 100644 spec/mspec/spec/expectations/should.rb create mode 100644 spec/mspec/spec/expectations/should_spec.rb create mode 100644 spec/mspec/spec/fixtures/a_spec.rb create mode 100644 spec/mspec/spec/fixtures/b_spec.rb create mode 100644 spec/mspec/spec/fixtures/config.mspec create mode 100755 spec/mspec/spec/fixtures/my_ruby create mode 100644 spec/mspec/spec/fixtures/print_interpreter_spec.rb create mode 100644 spec/mspec/spec/fixtures/tagging_spec.rb create mode 100644 spec/mspec/spec/guards/block_device_spec.rb create mode 100644 spec/mspec/spec/guards/bug_spec.rb create mode 100644 spec/mspec/spec/guards/conflict_spec.rb create mode 100644 spec/mspec/spec/guards/endian_spec.rb create mode 100644 spec/mspec/spec/guards/feature_spec.rb create mode 100644 spec/mspec/spec/guards/guard_spec.rb create mode 100644 spec/mspec/spec/guards/platform_spec.rb create mode 100644 spec/mspec/spec/guards/quarantine_spec.rb create mode 100644 spec/mspec/spec/guards/superuser_spec.rb create mode 100644 spec/mspec/spec/guards/support_spec.rb create mode 100644 spec/mspec/spec/guards/user_spec.rb create mode 100644 spec/mspec/spec/guards/version_spec.rb create mode 100644 spec/mspec/spec/helpers/argf_spec.rb create mode 100644 spec/mspec/spec/helpers/argv_spec.rb create mode 100644 spec/mspec/spec/helpers/datetime_spec.rb create mode 100644 spec/mspec/spec/helpers/fixture_spec.rb create mode 100644 spec/mspec/spec/helpers/flunk_spec.rb create mode 100644 spec/mspec/spec/helpers/fs_spec.rb create mode 100644 spec/mspec/spec/helpers/io_spec.rb create mode 100644 spec/mspec/spec/helpers/mock_to_path_spec.rb create mode 100644 spec/mspec/spec/helpers/numeric_spec.rb create mode 100644 spec/mspec/spec/helpers/ruby_exe_spec.rb create mode 100644 spec/mspec/spec/helpers/scratch_spec.rb create mode 100644 spec/mspec/spec/helpers/tmp_spec.rb create mode 100644 spec/mspec/spec/integration/interpreter_spec.rb create mode 100644 spec/mspec/spec/integration/run_spec.rb create mode 100644 spec/mspec/spec/integration/tag_spec.rb create mode 100644 spec/mspec/spec/matchers/base_spec.rb create mode 100644 spec/mspec/spec/matchers/be_an_instance_of_spec.rb create mode 100644 spec/mspec/spec/matchers/be_ancestor_of_spec.rb create mode 100644 spec/mspec/spec/matchers/be_close_spec.rb create mode 100644 spec/mspec/spec/matchers/be_computed_by_spec.rb create mode 100644 spec/mspec/spec/matchers/be_empty_spec.rb create mode 100644 spec/mspec/spec/matchers/be_false_spec.rb create mode 100644 spec/mspec/spec/matchers/be_kind_of_spec.rb create mode 100644 spec/mspec/spec/matchers/be_nan_spec.rb create mode 100644 spec/mspec/spec/matchers/be_nil_spec.rb create mode 100644 spec/mspec/spec/matchers/be_true_or_false_spec.rb create mode 100644 spec/mspec/spec/matchers/be_true_spec.rb create mode 100644 spec/mspec/spec/matchers/block_caller_spec.rb create mode 100644 spec/mspec/spec/matchers/complain_spec.rb create mode 100644 spec/mspec/spec/matchers/eql_spec.rb create mode 100644 spec/mspec/spec/matchers/equal_element_spec.rb create mode 100644 spec/mspec/spec/matchers/equal_spec.rb create mode 100644 spec/mspec/spec/matchers/have_class_variable_spec.rb create mode 100644 spec/mspec/spec/matchers/have_constant_spec.rb create mode 100644 spec/mspec/spec/matchers/have_instance_method_spec.rb create mode 100644 spec/mspec/spec/matchers/have_instance_variable_spec.rb create mode 100644 spec/mspec/spec/matchers/have_method_spec.rb create mode 100644 spec/mspec/spec/matchers/have_private_instance_method_spec.rb create mode 100644 spec/mspec/spec/matchers/have_private_method_spec.rb create mode 100644 spec/mspec/spec/matchers/have_protected_instance_method_spec.rb create mode 100644 spec/mspec/spec/matchers/have_public_instance_method_spec.rb create mode 100644 spec/mspec/spec/matchers/have_singleton_method_spec.rb create mode 100644 spec/mspec/spec/matchers/include_spec.rb create mode 100644 spec/mspec/spec/matchers/infinity_spec.rb create mode 100644 spec/mspec/spec/matchers/match_yaml_spec.rb create mode 100644 spec/mspec/spec/matchers/output_spec.rb create mode 100644 spec/mspec/spec/matchers/output_to_fd_spec.rb create mode 100644 spec/mspec/spec/matchers/raise_error_spec.rb create mode 100644 spec/mspec/spec/matchers/respond_to_spec.rb create mode 100644 spec/mspec/spec/matchers/signed_zero_spec.rb create mode 100644 spec/mspec/spec/mocks/mock_spec.rb create mode 100644 spec/mspec/spec/mocks/proxy_spec.rb create mode 100644 spec/mspec/spec/runner/actions/filter_spec.rb create mode 100644 spec/mspec/spec/runner/actions/tag_spec.rb create mode 100644 spec/mspec/spec/runner/actions/taglist_spec.rb create mode 100644 spec/mspec/spec/runner/actions/tagpurge_spec.rb create mode 100644 spec/mspec/spec/runner/actions/tally_spec.rb create mode 100644 spec/mspec/spec/runner/actions/timer_spec.rb create mode 100644 spec/mspec/spec/runner/context_spec.rb create mode 100644 spec/mspec/spec/runner/example_spec.rb create mode 100644 spec/mspec/spec/runner/exception_spec.rb create mode 100644 spec/mspec/spec/runner/filters/a.yaml create mode 100644 spec/mspec/spec/runner/filters/b.yaml create mode 100644 spec/mspec/spec/runner/filters/match_spec.rb create mode 100644 spec/mspec/spec/runner/filters/profile_spec.rb create mode 100644 spec/mspec/spec/runner/filters/regexp_spec.rb create mode 100644 spec/mspec/spec/runner/filters/tag_spec.rb create mode 100644 spec/mspec/spec/runner/formatters/describe_spec.rb create mode 100644 spec/mspec/spec/runner/formatters/dotted_spec.rb create mode 100644 spec/mspec/spec/runner/formatters/file_spec.rb create mode 100644 spec/mspec/spec/runner/formatters/html_spec.rb create mode 100644 spec/mspec/spec/runner/formatters/junit_spec.rb create mode 100644 spec/mspec/spec/runner/formatters/method_spec.rb create mode 100644 spec/mspec/spec/runner/formatters/multi_spec.rb create mode 100644 spec/mspec/spec/runner/formatters/specdoc_spec.rb create mode 100644 spec/mspec/spec/runner/formatters/spinner_spec.rb create mode 100644 spec/mspec/spec/runner/formatters/summary_spec.rb create mode 100644 spec/mspec/spec/runner/formatters/unit_spec.rb create mode 100644 spec/mspec/spec/runner/formatters/yaml_spec.rb create mode 100644 spec/mspec/spec/runner/mspec_spec.rb create mode 100644 spec/mspec/spec/runner/shared_spec.rb create mode 100644 spec/mspec/spec/runner/tag_spec.rb create mode 100644 spec/mspec/spec/runner/tags.txt create mode 100644 spec/mspec/spec/spec_helper.rb create mode 100644 spec/mspec/spec/utils/deprecate_spec.rb create mode 100644 spec/mspec/spec/utils/name_map_spec.rb create mode 100644 spec/mspec/spec/utils/options_spec.rb create mode 100644 spec/mspec/spec/utils/script_spec.rb create mode 100644 spec/mspec/spec/utils/version_spec.rb create mode 100644 spec/mspec/tool/remove_old_guards.rb (limited to 'spec/mspec') diff --git a/spec/mspec/.gitignore b/spec/mspec/.gitignore new file mode 100644 index 0000000000..5c5ecd9731 --- /dev/null +++ b/spec/mspec/.gitignore @@ -0,0 +1,26 @@ +pkg +*.rbc +*.iml +*.iws +*.ipr +*.sw? + +.rbx + +# ctags dir +/tags + +*.gem +.bundle +.config +.yardoc +InstalledFiles +_yardoc +coverage +doc/ +lib/bundler/man +rdoc +spec/reports +test/tmp +test/version_tmp +tmp diff --git a/spec/mspec/.travis.yml b/spec/mspec/.travis.yml new file mode 100644 index 0000000000..26a21e3aeb --- /dev/null +++ b/spec/mspec/.travis.yml @@ -0,0 +1,9 @@ +sudo: false +language: ruby +script: + - bundle exec rspec +rvm: + - 2.2.7 + - 2.3.4 + - 2.4.1 + - ruby-head diff --git a/spec/mspec/Gemfile b/spec/mspec/Gemfile new file mode 100644 index 0000000000..82a7d1678b --- /dev/null +++ b/spec/mspec/Gemfile @@ -0,0 +1,4 @@ +source 'https://rubygems.org' + +# Specify your gem's dependencies in mspec.gemspec +gemspec diff --git a/spec/mspec/Gemfile.lock b/spec/mspec/Gemfile.lock new file mode 100644 index 0000000000..ddd7fde498 --- /dev/null +++ b/spec/mspec/Gemfile.lock @@ -0,0 +1,30 @@ +PATH + remote: . + specs: + mspec (1.8.0) + +GEM + remote: https://rubygems.org/ + specs: + diff-lcs (1.2.5) + rake (10.4.2) + rspec (2.14.1) + rspec-core (~> 2.14.0) + rspec-expectations (~> 2.14.0) + rspec-mocks (~> 2.14.0) + rspec-core (2.14.8) + rspec-expectations (2.14.5) + diff-lcs (>= 1.1.3, < 2.0) + rspec-mocks (2.14.6) + +PLATFORMS + java + ruby + +DEPENDENCIES + mspec! + rake (~> 10.0) + rspec (~> 2.14.1) + +BUNDLED WITH + 1.10.2 diff --git a/spec/mspec/LICENSE b/spec/mspec/LICENSE new file mode 100644 index 0000000000..d581dd1c9f --- /dev/null +++ b/spec/mspec/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2008 Engine Yard, Inc. All rights reserved. + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/spec/mspec/README.md b/spec/mspec/README.md new file mode 100644 index 0000000000..8420fc1fce --- /dev/null +++ b/spec/mspec/README.md @@ -0,0 +1,88 @@ +[![Build Status](https://travis-ci.org/ruby/mspec.svg?branch=master)](https://travis-ci.org/ruby/mspec) + +## Overview + +MSpec is a specialized framework that is syntax-compatible with RSpec for +basic things like 'describe', 'it' blocks and 'before', 'after' actions. MSpec +contains additional features that assist in writing the RubySpecs used by +multiple Ruby implementations. + +MSpec attempts to use the simplest Ruby language features so that beginning +Ruby implementations can run the Ruby specs. + +MSpec is not intended as a replacement for RSpec. MSpec attempts to provide a +subset of RSpec's features in some cases and a superset in others. It does not +provide all the matchers, for instance. + +However, MSpec provides several extensions to facilitate writing the Ruby +specs in a manner compatible with multiple Ruby implementations. + + 1. MSpec offers a set of guards to control execution of the specs. These + guards not only enable or disable execution but also annotate the specs + with additional information about why they are run or not run. + + 2. MSpec provides a different shared spec implementation specifically + designed to ease writing specs for the numerous aliased methods in Ruby. + The MSpec shared spec implementation should not conflict with RSpec's own + shared behavior facility. + + 3. MSpec provides various helper methods to simplify some specs, for + example, creating temporary file names. + + 4. MSpec has several specialized runner scripts that includes a + configuration facility with a default project file and user-specific + overrides. + + +## Bundler + +A Gemfile is provided. Use Bundler to install gem dependencies. To install +Bundler, run the following: + +```bash +gem install bundler +``` + +To install the gem dependencies with Bundler, run the following: + +```bash +ruby -S bundle install +``` + +## Running Specs + +Use RSpec to run the MSpec specs. There are no plans currently to make the +MSpec specs runnable by MSpec. + +After installing the gem dependencies, the specs can be run as follows: + +```bash +ruby -S bundle exec rspec +``` + +Or + +```bash +ruby -S rake +``` + +To run an individual spec file, use the following example: + +```bash +ruby -S bundle exec rspec spec/helpers/ruby_exe_spec.rb +``` + + +## Documentation + +See http://ruby.github.io/rubyspec.github.io/ + + +## Source Code + +See https://github.com/ruby/mspec + + +## License + +See the LICENSE in the source code. diff --git a/spec/mspec/Rakefile b/spec/mspec/Rakefile new file mode 100644 index 0000000000..0e294cde8e --- /dev/null +++ b/spec/mspec/Rakefile @@ -0,0 +1,7 @@ +require 'bundler/gem_tasks' +require 'bundler/setup' +require 'rspec/core/rake_task' + +RSpec::Core::RakeTask.new(:spec) + +task :default => :spec diff --git a/spec/mspec/bin/mkspec b/spec/mspec/bin/mkspec new file mode 100755 index 0000000000..00f1fdff47 --- /dev/null +++ b/spec/mspec/bin/mkspec @@ -0,0 +1,7 @@ +#!/usr/bin/env ruby + +$:.unshift File.expand_path('../../lib', __FILE__) + +require 'mspec/commands/mkspec' + +MkSpec.main diff --git a/spec/mspec/bin/mkspec.bat b/spec/mspec/bin/mkspec.bat new file mode 100755 index 0000000000..1073d20a9b --- /dev/null +++ b/spec/mspec/bin/mkspec.bat @@ -0,0 +1 @@ +@"ruby.exe" "%~dpn0" %* diff --git a/spec/mspec/bin/mspec b/spec/mspec/bin/mspec new file mode 100755 index 0000000000..f833257bb0 --- /dev/null +++ b/spec/mspec/bin/mspec @@ -0,0 +1,7 @@ +#!/usr/bin/env ruby + +$:.unshift File.expand_path('../../lib', __FILE__) + +require 'mspec/commands/mspec' + +MSpecMain.main diff --git a/spec/mspec/bin/mspec-ci b/spec/mspec/bin/mspec-ci new file mode 100755 index 0000000000..d7cd50a827 --- /dev/null +++ b/spec/mspec/bin/mspec-ci @@ -0,0 +1,7 @@ +#!/usr/bin/env ruby + +$:.unshift File.expand_path('../../lib', __FILE__) + +require 'mspec/commands/mspec-ci' + +MSpecCI.main diff --git a/spec/mspec/bin/mspec-ci.bat b/spec/mspec/bin/mspec-ci.bat new file mode 100755 index 0000000000..1073d20a9b --- /dev/null +++ b/spec/mspec/bin/mspec-ci.bat @@ -0,0 +1 @@ +@"ruby.exe" "%~dpn0" %* diff --git a/spec/mspec/bin/mspec-run b/spec/mspec/bin/mspec-run new file mode 100755 index 0000000000..010ecefe35 --- /dev/null +++ b/spec/mspec/bin/mspec-run @@ -0,0 +1,7 @@ +#!/usr/bin/env ruby + +$:.unshift File.expand_path('../../lib', __FILE__) + +require 'mspec/commands/mspec-run' + +MSpecRun.main diff --git a/spec/mspec/bin/mspec-run.bat b/spec/mspec/bin/mspec-run.bat new file mode 100755 index 0000000000..1073d20a9b --- /dev/null +++ b/spec/mspec/bin/mspec-run.bat @@ -0,0 +1 @@ +@"ruby.exe" "%~dpn0" %* diff --git a/spec/mspec/bin/mspec-tag b/spec/mspec/bin/mspec-tag new file mode 100755 index 0000000000..a5f9fffaaa --- /dev/null +++ b/spec/mspec/bin/mspec-tag @@ -0,0 +1,7 @@ +#!/usr/bin/env ruby + +$:.unshift File.expand_path('../../lib', __FILE__) + +require 'mspec/commands/mspec-tag' + +MSpecTag.main diff --git a/spec/mspec/bin/mspec-tag.bat b/spec/mspec/bin/mspec-tag.bat new file mode 100755 index 0000000000..1073d20a9b --- /dev/null +++ b/spec/mspec/bin/mspec-tag.bat @@ -0,0 +1 @@ +@"ruby.exe" "%~dpn0" %* diff --git a/spec/mspec/bin/mspec.bat b/spec/mspec/bin/mspec.bat new file mode 100755 index 0000000000..1073d20a9b --- /dev/null +++ b/spec/mspec/bin/mspec.bat @@ -0,0 +1 @@ +@"ruby.exe" "%~dpn0" %* diff --git a/spec/mspec/lib/mspec.rb b/spec/mspec/lib/mspec.rb new file mode 100644 index 0000000000..42d590c99a --- /dev/null +++ b/spec/mspec/lib/mspec.rb @@ -0,0 +1,20 @@ +require 'mspec/matchers' +require 'mspec/expectations' +require 'mspec/mocks' +require 'mspec/runner' +require 'mspec/guards' +require 'mspec/helpers' +require 'mspec/version' + +# If the implementation on which the specs are run cannot +# load pp from the standard library, add a pp.rb file that +# defines the #pretty_inspect method on Object or Kernel. +begin + require 'pp' +rescue LoadError + module Kernel + def pretty_inspect + inspect + end + end +end diff --git a/spec/mspec/lib/mspec/commands/mkspec.rb b/spec/mspec/lib/mspec/commands/mkspec.rb new file mode 100755 index 0000000000..7a943aa1fe --- /dev/null +++ b/spec/mspec/lib/mspec/commands/mkspec.rb @@ -0,0 +1,155 @@ +#!/usr/bin/env ruby + +require 'rbconfig' +require 'mspec/version' +require 'mspec/utils/options' +require 'mspec/utils/name_map' +require 'mspec/helpers/fs' + +class MkSpec + attr_reader :config + + def initialize + @config = { + :constants => [], + :requires => [], + :base => "core", + :version => nil + } + @map = NameMap.new true + end + + def options(argv=ARGV) + options = MSpecOptions.new "mkspec [options]", 32 + + options.on("-c", "--constant", "CONSTANT", + "Class or Module to generate spec stubs for") do |name| + config[:constants] << name + end + options.on("-b", "--base", "DIR", + "Directory to generate specs into") do |directory| + config[:base] = File.expand_path directory + end + options.on("-r", "--require", "LIBRARY", + "A library to require") do |file| + config[:requires] << file + end + options.on("-V", "--version-guard", "VERSION", + "Specify version for ruby_version_is guards") do |version| + config[:version] = version + end + options.version MSpec::VERSION + options.help + + options.doc "\n How might this work in the real world?\n" + options.doc " 1. To create spec stubs for every class or module in Object\n" + options.doc " $ mkspec\n" + options.doc " 2. To create spec stubs for Fixnum\n" + options.doc " $ mkspec -c Fixnum\n" + options.doc " 3. To create spec stubs for Complex in 'superspec/complex'\n" + options.doc " $ mkspec -c Complex -r complex -b superspec" + options.doc "" + + options.parse argv + end + + def create_directory(mod) + subdir = @map.dir_name mod, config[:base] + + if File.exist? subdir + unless File.directory? subdir + puts "#{subdir} already exists and is not a directory." + return nil + end + else + mkdir_p subdir + end + + subdir + end + + def write_requires(dir, file) + prefix = config[:base] + '/' + raise dir unless dir.start_with? prefix + sub = dir[prefix.size..-1] + parents = '../' * (sub.split('/').length + 1) + + File.open(file, 'w') do |f| + f.puts "require File.expand_path('../#{parents}spec_helper', __FILE__)" + config[:requires].each do |lib| + f.puts "require '#{lib}'" + end + end + end + + def write_version(f) + f.puts "" + if version = config[:version] + f.puts "ruby_version_is #{version} do" + yield " " + f.puts "end" + else + yield "" + end + end + + def write_spec(file, meth, exists) + if exists + out = `#{ruby} #{MSPEC_HOME}/bin/mspec-run --dry-run --unguarded -fs -e '#{meth}' #{file}` + return if out.include?(meth) + end + + File.open file, 'a' do |f| + write_version(f) do |indent| + f.puts <<-EOS +#{indent}describe "#{meth}" do +#{indent} it "needs to be reviewed for spec completeness" +#{indent}end +EOS + end + end + + puts file + end + + def create_file(dir, mod, meth, name) + file = File.join dir, @map.file_name(meth, mod) + exists = File.exist? file + + write_requires dir, file unless exists + write_spec file, name, exists + end + + def run + config[:requires].each { |lib| require lib } + constants = config[:constants] + constants = Object.constants if constants.empty? + + @map.map({}, constants).each do |mod, methods| + name = mod.chop + next unless dir = create_directory(name) + + methods.each { |method| create_file dir, name, method, mod + method } + end + end + + ## + # Determine and return the path of the ruby executable. + + def ruby + ruby = File.join(RbConfig::CONFIG['bindir'], + RbConfig::CONFIG['ruby_install_name']) + + ruby.gsub! File::SEPARATOR, File::ALT_SEPARATOR if File::ALT_SEPARATOR + + return ruby + end + + def self.main + ENV['MSPEC_RUNNER'] = '1' + + script = new + script.options + script.run + end +end diff --git a/spec/mspec/lib/mspec/commands/mspec-ci.rb b/spec/mspec/lib/mspec/commands/mspec-ci.rb new file mode 100644 index 0000000000..225d2bb96d --- /dev/null +++ b/spec/mspec/lib/mspec/commands/mspec-ci.rb @@ -0,0 +1,79 @@ +#!/usr/bin/env ruby + +$:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib') + +require 'mspec/version' +require 'mspec/utils/options' +require 'mspec/utils/script' + + +class MSpecCI < MSpecScript + def options(argv=ARGV) + options = MSpecOptions.new "mspec ci [options] (FILE|DIRECTORY|GLOB)+", 30, config + + options.doc " Ask yourself:" + options.doc " 1. How to run the specs?" + options.doc " 2. How to modify the guard behavior?" + options.doc " 2. How to display the output?" + options.doc " 3. What action to perform?" + options.doc " 4. When to perform it?" + + options.doc "\n How to run the specs" + options.chdir + options.prefix + options.configure { |f| load f } + options.name + options.pretend + options.interrupt + + options.doc "\n How to modify the guard behavior" + options.unguarded + options.verify + + options.doc "\n How to display their output" + options.formatters + options.verbose + + options.doc "\n What action to perform" + options.actions + + options.doc "\n When to perform it" + options.action_filters + + options.doc "\n Help!" + options.debug + options.version MSpec::VERSION + options.help + + options.doc "\n Custom options" + custom_options options + + options.doc "\n How might this work in the real world?" + options.doc "\n 1. To simply run the known good specs" + options.doc "\n $ mspec ci" + options.doc "\n 2. To run a subset of the known good specs" + options.doc "\n $ mspec ci path/to/specs" + options.doc "\n 3. To start the debugger before the spec matching 'this crashes'" + options.doc "\n $ mspec ci --spec-debug -S 'this crashes'" + options.doc "" + + patterns = options.parse argv + patterns = config[:ci_files] if patterns.empty? + @files = files patterns + end + + def run + MSpec.register_tags_patterns config[:tags_patterns] + MSpec.register_files @files + + tags = ["fails", "critical", "unstable", "incomplete", "unsupported"] + tags += Array(config[:ci_xtags]) + + require 'mspec/runner/filters/tag' + filter = TagFilter.new(:exclude, *tags) + filter.register + + MSpec.process + exit MSpec.exit_code + end +end diff --git a/spec/mspec/lib/mspec/commands/mspec-run.rb b/spec/mspec/lib/mspec/commands/mspec-run.rb new file mode 100644 index 0000000000..45b26e88ad --- /dev/null +++ b/spec/mspec/lib/mspec/commands/mspec-run.rb @@ -0,0 +1,87 @@ +#!/usr/bin/env ruby + +$:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib') + +require 'mspec/version' +require 'mspec/utils/options' +require 'mspec/utils/script' + + +class MSpecRun < MSpecScript + def initialize + super + + config[:files] = [] + end + + def options(argv=ARGV) + options = MSpecOptions.new "mspec run [options] (FILE|DIRECTORY|GLOB)+", 30, config + + options.doc " Ask yourself:" + options.doc " 1. What specs to run?" + options.doc " 2. How to modify the execution?" + options.doc " 3. How to modify the guard behavior?" + options.doc " 4. How to display the output?" + options.doc " 5. What action to perform?" + options.doc " 6. When to perform it?" + + options.doc "\n What specs to run" + options.filters + + options.doc "\n How to modify the execution" + options.chdir + options.prefix + options.configure { |f| load f } + options.name + options.randomize + options.repeat + options.pretend + options.interrupt + + options.doc "\n How to modify the guard behavior" + options.unguarded + options.verify + + options.doc "\n How to display their output" + options.formatters + options.verbose + + options.doc "\n What action to perform" + options.actions + + options.doc "\n When to perform it" + options.action_filters + + options.doc "\n Help!" + options.debug + options.version MSpec::VERSION + options.help + + options.doc "\n Custom options" + custom_options options + + options.doc "\n How might this work in the real world?" + options.doc "\n 1. To simply run some specs" + options.doc "\n $ mspec path/to/the/specs" + options.doc " mspec path/to/the_file_spec.rb" + options.doc "\n 2. To run specs tagged with 'fails'" + options.doc "\n $ mspec -g fails path/to/the_file_spec.rb" + options.doc "\n 3. To start the debugger before the spec matching 'this crashes'" + options.doc "\n $ mspec --spec-debug -S 'this crashes' path/to/the_file_spec.rb" + options.doc "\n 4. To run some specs matching 'this crashes'" + options.doc "\n $ mspec -e 'this crashes' path/to/the_file_spec.rb" + + options.doc "" + + patterns = options.parse argv + @files = files_from_patterns(patterns) + end + + def run + MSpec.register_tags_patterns config[:tags_patterns] + MSpec.register_files @files + + MSpec.process + exit MSpec.exit_code + end +end diff --git a/spec/mspec/lib/mspec/commands/mspec-tag.rb b/spec/mspec/lib/mspec/commands/mspec-tag.rb new file mode 100644 index 0000000000..7582015916 --- /dev/null +++ b/spec/mspec/lib/mspec/commands/mspec-tag.rb @@ -0,0 +1,133 @@ +#!/usr/bin/env ruby + +require 'mspec/version' +require 'mspec/utils/options' +require 'mspec/utils/script' + + +class MSpecTag < MSpecScript + def initialize + super + + config[:tagger] = :add + config[:tag] = 'fails:' + config[:outcome] = :fail + config[:ltags] = [] + end + + def options(argv=ARGV) + options = MSpecOptions.new "mspec tag [options] (FILE|DIRECTORY|GLOB)+", 30, config + + options.doc " Ask yourself:" + options.doc " 1. What specs to run?" + options.doc " 2. How to modify the execution?" + options.doc " 3. How to display the output?" + options.doc " 4. What tag action to perform?" + options.doc " 5. When to perform it?" + + options.doc "\n What specs to run" + options.filters + + options.doc "\n How to modify the execution" + options.configure { |f| load f } + options.name + options.pretend + options.unguarded + options.interrupt + + options.doc "\n How to display their output" + options.formatters + options.verbose + + options.doc "\n What action to perform and when to perform it" + options.on("-N", "--add", "TAG", + "Add TAG with format 'tag' or 'tag(comment)' (see -Q, -F, -L)") do |o| + config[:tagger] = :add + config[:tag] = "#{o}:" + end + options.on("-R", "--del", "TAG", + "Delete TAG (see -Q, -F, -L)") do |o| + config[:tagger] = :del + config[:tag] = "#{o}:" + config[:outcome] = :pass + end + options.on("-Q", "--pass", "Apply action to specs that pass (default for --del)") do + config[:outcome] = :pass + end + options.on("-F", "--fail", "Apply action to specs that fail (default for --add)") do + config[:outcome] = :fail + end + options.on("-L", "--all", "Apply action to all specs") do + config[:outcome] = :all + end + options.on("--list", "TAG", "Display descriptions of any specs tagged with TAG") do |t| + config[:tagger] = :list + config[:ltags] << t + end + options.on("--list-all", "Display descriptions of any tagged specs") do + config[:tagger] = :list_all + end + options.on("--purge", "Remove all tags not matching any specs") do + config[:tagger] = :purge + end + + options.doc "\n Help!" + options.debug + options.version MSpec::VERSION + options.help + + options.doc "\n Custom options" + custom_options options + + options.doc "\n How might this work in the real world?" + options.doc "\n 1. To add the 'fails' tag to failing specs" + options.doc "\n $ mspec tag path/to/the_file_spec.rb" + options.doc "\n 2. To remove the 'fails' tag from passing specs" + options.doc "\n $ mspec tag --del fails path/to/the_file_spec.rb" + options.doc "\n 3. To display the descriptions for all specs tagged with 'fails'" + options.doc "\n $ mspec tag --list fails path/to/the/specs" + options.doc "" + + patterns = options.parse argv + if patterns.empty? + puts options + puts "No files specified." + exit 1 + end + @files = files patterns + end + + def register + require 'mspec/runner/actions' + + case config[:tagger] + when :add, :del + tag = SpecTag.new config[:tag] + tagger = TagAction.new(config[:tagger], config[:outcome], tag.tag, tag.comment, + config[:atags], config[:astrings]) + when :list, :list_all + tagger = TagListAction.new config[:tagger] == :list_all ? nil : config[:ltags] + MSpec.register_mode :pretend + config[:formatter] = false + when :purge + tagger = TagPurgeAction.new + MSpec.register_mode :pretend + MSpec.register_mode :unguarded + config[:formatter] = false + else + raise ArgumentError, "No recognized action given" + end + tagger.register + + super + end + + def run + MSpec.register_tags_patterns config[:tags_patterns] + MSpec.register_files @files + + MSpec.process + exit MSpec.exit_code + end +end + diff --git a/spec/mspec/lib/mspec/commands/mspec.rb b/spec/mspec/lib/mspec/commands/mspec.rb new file mode 100755 index 0000000000..6f1ae8cb6e --- /dev/null +++ b/spec/mspec/lib/mspec/commands/mspec.rb @@ -0,0 +1,163 @@ +#!/usr/bin/env ruby + +require 'mspec/version' +require 'mspec/utils/options' +require 'mspec/utils/script' +require 'mspec/helpers/tmp' +require 'mspec/runner/actions/filter' +require 'mspec/runner/actions/timer' + + +class MSpecMain < MSpecScript + def initialize + super + + config[:loadpath] = [] + config[:requires] = [] + config[:target] = ENV['RUBY'] || 'ruby' + config[:flags] = [] + config[:command] = nil + config[:options] = [] + config[:launch] = [] + end + + def options(argv=ARGV) + config[:command] = argv.shift if ["ci", "run", "tag"].include?(argv[0]) + + options = MSpecOptions.new "mspec [COMMAND] [options] (FILE|DIRECTORY|GLOB)+", 30, config + + options.doc " The mspec command sets up and invokes the sub-commands" + options.doc " (see below) to enable, for instance, running the specs" + options.doc " with different implementations like ruby, jruby, rbx, etc.\n" + + options.configure do |f| + load f + config[:options] << '-B' << f + end + + options.targets + + options.on("--warnings", "Don't supress warnings") do + config[:flags] << '-w' + ENV['OUTPUT_WARNINGS'] = '1' + end + + options.on("-j", "--multi", "Run multiple (possibly parallel) subprocesses") do + config[:multi] = true + config[:options] << "-fy" + end + + options.version MSpec::VERSION do + if config[:command] + config[:options] << "-v" + else + puts "#{File.basename $0} #{MSpec::VERSION}" + exit + end + end + + options.help do + if config[:command] + config[:options] << "-h" + else + puts options + exit 1 + end + end + + options.doc "\n Custom options" + custom_options options + + # The rest of the help output + options.doc "\n where COMMAND is one of:\n" + options.doc " run - Run the specified specs (default)" + options.doc " ci - Run the known good specs" + options.doc " tag - Add or remove tags\n" + options.doc " mspec COMMAND -h for more options\n" + options.doc " example: $ mspec run -h\n" + + options.on_extra { |o| config[:options] << o } + options.parse(argv) + + if config[:multi] + options = MSpecOptions.new "mspec", 30, config + options.all + patterns = options.parse(config[:options]) + @files = files_from_patterns(patterns) + end + end + + def register; end + + def multi_exec(argv) + MSpec.register_files @files + + require 'mspec/runner/formatters/multi' + formatter = MultiFormatter.new + + output_files = [] + processes = [cores, @files.size].min + children = processes.times.map { |i| + name = tmp "mspec-multi-#{i}" + output_files << name + + env = { + "SPEC_TEMP_DIR" => "rubyspec_temp_#{i}", + "MSPEC_MULTI" => i.to_s + } + command = argv + ["-o", name] + $stderr.puts "$ #{command.join(' ')}" if $MSPEC_DEBUG + IO.popen([env, *command], "rb+") + } + + puts children.map { |child| child.gets }.uniq + formatter.start + + until @files.empty? + IO.select(children)[0].each { |io| + reply = io.read(1) + case reply + when '.' + formatter.unload + when nil + raise "Worker died!" + else + while chunk = (io.read_nonblock(4096) rescue nil) + reply += chunk + end + raise reply + end + io.puts @files.shift unless @files.empty? + } + end + + ok = true + children.each { |child| + child.puts "QUIT" + Process.wait(child.pid) + ok &&= $?.success? + } + + formatter.aggregate_results(output_files) + formatter.finish + ok + end + + def run + argv = config[:target].split(/\s+/) + + argv.concat config[:launch] + argv.concat config[:flags] + argv.concat config[:loadpath] + argv.concat config[:requires] + argv << "#{MSPEC_HOME}/bin/mspec-#{ config[:command] || "run" }" + argv.concat config[:options] + + if config[:multi] + exit multi_exec(argv) + else + $stderr.puts "$ #{argv.join(' ')}" + exec(*argv) + end + end +end diff --git a/spec/mspec/lib/mspec/expectations.rb b/spec/mspec/lib/mspec/expectations.rb new file mode 100644 index 0000000000..d07f959b27 --- /dev/null +++ b/spec/mspec/lib/mspec/expectations.rb @@ -0,0 +1,2 @@ +require 'mspec/expectations/expectations' +require 'mspec/expectations/should' diff --git a/spec/mspec/lib/mspec/expectations/expectations.rb b/spec/mspec/lib/mspec/expectations/expectations.rb new file mode 100644 index 0000000000..cfdc2b63a3 --- /dev/null +++ b/spec/mspec/lib/mspec/expectations/expectations.rb @@ -0,0 +1,21 @@ +class SpecExpectationNotMetError < StandardError +end + +class SpecExpectationNotFoundError < StandardError + def message + "No behavior expectation was found in the example" + end +end + +class SpecExpectation + def self.fail_with(expected, actual) + expected_to_s = expected.to_s + actual_to_s = actual.to_s + if expected_to_s.size + actual_to_s.size > 80 + message = "#{expected_to_s.chomp}\n#{actual_to_s}" + else + message = "#{expected_to_s} #{actual_to_s}" + end + Kernel.raise SpecExpectationNotMetError, message + end +end diff --git a/spec/mspec/lib/mspec/expectations/should.rb b/spec/mspec/lib/mspec/expectations/should.rb new file mode 100644 index 0000000000..f6d83053f5 --- /dev/null +++ b/spec/mspec/lib/mspec/expectations/should.rb @@ -0,0 +1,29 @@ +class Object + NO_MATCHER_GIVEN = Object.new + + def should(matcher = NO_MATCHER_GIVEN) + MSpec.expectation + MSpec.actions :expectation, MSpec.current.state + unless matcher.equal? NO_MATCHER_GIVEN + unless matcher.matches? self + expected, actual = matcher.failure_message + SpecExpectation.fail_with(expected, actual) + end + else + SpecPositiveOperatorMatcher.new(self) + end + end + + def should_not(matcher = NO_MATCHER_GIVEN) + MSpec.expectation + MSpec.actions :expectation, MSpec.current.state + unless matcher.equal? NO_MATCHER_GIVEN + if matcher.matches? self + expected, actual = matcher.negative_failure_message + SpecExpectation.fail_with(expected, actual) + end + else + SpecNegativeOperatorMatcher.new(self) + end + end +end diff --git a/spec/mspec/lib/mspec/guards.rb b/spec/mspec/lib/mspec/guards.rb new file mode 100644 index 0000000000..0d7d300c64 --- /dev/null +++ b/spec/mspec/lib/mspec/guards.rb @@ -0,0 +1,12 @@ +require 'mspec/utils/ruby_name' +require 'mspec/guards/block_device' +require 'mspec/guards/bug' +require 'mspec/guards/conflict' +require 'mspec/guards/endian' +require 'mspec/guards/feature' +require 'mspec/guards/guard' +require 'mspec/guards/platform' +require 'mspec/guards/quarantine' +require 'mspec/guards/support' +require 'mspec/guards/superuser' +require 'mspec/guards/version' diff --git a/spec/mspec/lib/mspec/guards/block_device.rb b/spec/mspec/lib/mspec/guards/block_device.rb new file mode 100644 index 0000000000..327f6e564e --- /dev/null +++ b/spec/mspec/lib/mspec/guards/block_device.rb @@ -0,0 +1,18 @@ +require 'mspec/guards/guard' + +class BlockDeviceGuard < SpecGuard + def match? + platform_is_not :freebsd, :windows, :opal do + block = `find /dev /devices -type b 2> /dev/null` + return !(block.nil? || block.empty?) + end + + false + end +end + +class Object + def with_block_device(&block) + BlockDeviceGuard.new.run_if(:with_block_device, &block) + end +end diff --git a/spec/mspec/lib/mspec/guards/bug.rb b/spec/mspec/lib/mspec/guards/bug.rb new file mode 100644 index 0000000000..31de6e080d --- /dev/null +++ b/spec/mspec/lib/mspec/guards/bug.rb @@ -0,0 +1,30 @@ +require 'mspec/guards/version' + +class BugGuard < VersionGuard + def initialize(bug, version) + @bug = bug + if String === version + MSpec.deprecate "ruby_bug with a single version", 'an exclusive range ("2.1"..."2.3")' + @version = SpecVersion.new version, true + else + super(version) + end + @parameters = [@bug, @version] + end + + def match? + return false if MSpec.mode? :no_ruby_bug + return false unless PlatformGuard.standard? + if Range === @version + super + else + FULL_RUBY_VERSION <= @version + end + end +end + +class Object + def ruby_bug(bug, version, &block) + BugGuard.new(bug, version).run_unless(:ruby_bug, &block) + end +end diff --git a/spec/mspec/lib/mspec/guards/conflict.rb b/spec/mspec/lib/mspec/guards/conflict.rb new file mode 100644 index 0000000000..c1d33e3512 --- /dev/null +++ b/spec/mspec/lib/mspec/guards/conflict.rb @@ -0,0 +1,19 @@ +require 'mspec/guards/guard' + +class ConflictsGuard < SpecGuard + def match? + # Always convert constants to symbols regardless of version. + constants = Object.constants.map { |x| x.to_sym } + @parameters.any? { |mod| constants.include? mod } + end +end + +class Object + # In some cases, libraries will modify another Ruby method's + # behavior. The specs for the method's behavior will then fail + # if that library is loaded. This guard will not run if any of + # the specified constants exist in Object.constants. + def conflicts_with(*modules, &block) + ConflictsGuard.new(*modules).run_unless(:conflicts_with, &block) + end +end diff --git a/spec/mspec/lib/mspec/guards/endian.rb b/spec/mspec/lib/mspec/guards/endian.rb new file mode 100644 index 0000000000..6bb01263c7 --- /dev/null +++ b/spec/mspec/lib/mspec/guards/endian.rb @@ -0,0 +1,27 @@ +require 'mspec/guards/guard' + +# Despite that these are inverses, the two classes are +# used to simplify MSpec guard reporting modes + +class EndianGuard < SpecGuard + def pattern + @pattern ||= [1].pack('L') + end + private :pattern +end + +class BigEndianGuard < EndianGuard + def match? + pattern[-1] == ?\001 + end +end + +class Object + def big_endian(&block) + BigEndianGuard.new.run_if(:big_endian, &block) + end + + def little_endian(&block) + BigEndianGuard.new.run_unless(:little_endian, &block) + end +end diff --git a/spec/mspec/lib/mspec/guards/feature.rb b/spec/mspec/lib/mspec/guards/feature.rb new file mode 100644 index 0000000000..346212bda0 --- /dev/null +++ b/spec/mspec/lib/mspec/guards/feature.rb @@ -0,0 +1,43 @@ +require 'mspec/guards/guard' + +class FeatureGuard < SpecGuard + def self.enabled?(*features) + new(*features).match? + end + + def match? + @parameters.all? { |f| MSpec.feature_enabled? f } + end +end + +class Object + # Provides better documentation in the specs by + # naming sets of features that work together as + # a whole. Examples include :encoding, :fiber, + # :continuation, :fork. + # + # Usage example: + # + # with_feature :encoding do + # # specs for a method that provides aspects + # # of the encoding feature + # end + # + # Multiple features must all be enabled for the + # guard to run: + # + # with_feature :one, :two do + # # these specs will run if features :one AND + # # :two are enabled. + # end + # + # The implementation must explicitly enable a feature + # by adding code like the following to the .mspec + # configuration file: + # + # MSpec.enable_feature :encoding + # + def with_feature(*features, &block) + FeatureGuard.new(*features).run_if(:with_feature, &block) + end +end diff --git a/spec/mspec/lib/mspec/guards/guard.rb b/spec/mspec/lib/mspec/guards/guard.rb new file mode 100644 index 0000000000..c95d8f7923 --- /dev/null +++ b/spec/mspec/lib/mspec/guards/guard.rb @@ -0,0 +1,118 @@ +require 'mspec/runner/mspec' +require 'mspec/runner/actions/tally' +require 'mspec/utils/ruby_name' + +class SpecGuard + def self.report + @report ||= Hash.new { |h,k| h[k] = [] } + end + + def self.clear + @report = nil + end + + def self.finish + report.keys.sort.each do |key| + desc = report[key] + size = desc.size + spec = size == 1 ? "spec" : "specs" + print "\n\n#{size} #{spec} omitted by guard: #{key}:\n" + desc.each { |description| print "\n", description; } + end + + print "\n\n" + end + + def self.guards + @guards ||= [] + end + + def self.clear_guards + @guards = [] + end + + # Returns a partial Ruby version string based on +which+. + # For example, if RUBY_VERSION = 8.2.3: + # + # :major => "8" + # :minor => "8.2" + # :tiny => "8.2.3" + # :teeny => "8.2.3" + # :full => "8.2.3" + def self.ruby_version(which = :minor) + case which + when :major + n = 1 + when :minor + n = 2 + when :tiny, :teeny, :full + n = 3 + end + + RUBY_VERSION.split('.')[0,n].join('.') + end + + attr_accessor :name + + def initialize(*args) + @parameters = args + end + + def yield?(invert = false) + return true if MSpec.mode? :unguarded + + allow = match? ^ invert + + if !allow and reporting? + MSpec.guard + MSpec.register :finish, SpecGuard + MSpec.register :add, self + return true + elsif MSpec.mode? :verify + return true + end + + allow + end + + def run_if(name, &block) + @name = name + yield if yield?(false) + ensure + unregister + end + + def run_unless(name, &block) + @name = name + yield if yield?(true) + ensure + unregister + end + + def reporting? + MSpec.mode?(:report) or + (MSpec.mode?(:report_on) and SpecGuard.guards.include?(name)) + end + + def report_key + "#{name} #{@parameters.join(", ")}" + end + + def record(description) + SpecGuard.report[report_key] << description + end + + def add(example) + record example.description + MSpec.retrieve(:formatter).tally.counter.guards! + end + + def unregister + MSpec.unguard + MSpec.unregister :add, self + end + + def match? + raise "must be implemented by the subclass" + end +end diff --git a/spec/mspec/lib/mspec/guards/platform.rb b/spec/mspec/lib/mspec/guards/platform.rb new file mode 100644 index 0000000000..875aef6c9c --- /dev/null +++ b/spec/mspec/lib/mspec/guards/platform.rb @@ -0,0 +1,78 @@ +require 'mspec/guards/guard' + +class PlatformGuard < SpecGuard + def self.implementation?(*args) + args.any? do |name| + case name + when :rubinius + RUBY_NAME.start_with?('rbx') + when :ruby, :jruby, :truffleruby, :ironruby, :macruby, :maglev, :topaz, :opal + RUBY_NAME.start_with?(name.to_s) + else + raise "unknown implementation #{name}" + end + end + end + + def self.standard? + implementation? :ruby + end + + HOST_OS = begin + require 'rbconfig' + RbConfig::CONFIG['host_os'] || RUBY_PLATFORM + rescue LoadError + RUBY_PLATFORM + end.downcase + + def self.os?(*oses) + oses.any? do |os| + raise ":java is not a valid OS" if os == :java + if os == :windows + HOST_OS =~ /(mswin|mingw)/ + else + HOST_OS.include?(os.to_s) + end + end + end + + def self.windows? + os?(:windows) + end + + def self.wordsize?(size) + size == 8 * 1.size + end + + def initialize(*args) + if args.last.is_a?(Hash) + @options, @platforms = args.last, args[0..-2] + else + @options, @platforms = {}, args + end + @parameters = args + end + + def match? + match = @platforms.empty? ? true : PlatformGuard.os?(*@platforms) + @options.each do |key, value| + case key + when :os + match &&= PlatformGuard.os?(*value) + when :wordsize + match &&= PlatformGuard.wordsize? value + end + end + match + end +end + +class Object + def platform_is(*args, &block) + PlatformGuard.new(*args).run_if(:platform_is, &block) + end + + def platform_is_not(*args, &block) + PlatformGuard.new(*args).run_unless(:platform_is_not, &block) + end +end diff --git a/spec/mspec/lib/mspec/guards/quarantine.rb b/spec/mspec/lib/mspec/guards/quarantine.rb new file mode 100644 index 0000000000..4724613a0f --- /dev/null +++ b/spec/mspec/lib/mspec/guards/quarantine.rb @@ -0,0 +1,13 @@ +require 'mspec/guards/guard' + +class QuarantineGuard < SpecGuard + def match? + true + end +end + +class Object + def quarantine!(&block) + QuarantineGuard.new.run_unless(:quarantine!, &block) + end +end diff --git a/spec/mspec/lib/mspec/guards/superuser.rb b/spec/mspec/lib/mspec/guards/superuser.rb new file mode 100644 index 0000000000..6e447198a7 --- /dev/null +++ b/spec/mspec/lib/mspec/guards/superuser.rb @@ -0,0 +1,17 @@ +require 'mspec/guards/guard' + +class SuperUserGuard < SpecGuard + def match? + Process.euid == 0 + end +end + +class Object + def as_superuser(&block) + SuperUserGuard.new.run_if(:as_superuser, &block) + end + + def as_user(&block) + SuperUserGuard.new.run_unless(:as_user, &block) + end +end diff --git a/spec/mspec/lib/mspec/guards/support.rb b/spec/mspec/lib/mspec/guards/support.rb new file mode 100644 index 0000000000..f1760ece2e --- /dev/null +++ b/spec/mspec/lib/mspec/guards/support.rb @@ -0,0 +1,16 @@ +require 'mspec/guards/platform' + +class SupportedGuard < SpecGuard + def match? + if @parameters.include? :ruby + raise Exception, "improper use of not_supported_on guard" + end + !PlatformGuard.standard? and PlatformGuard.implementation?(*@parameters) + end +end + +class Object + def not_supported_on(*args, &block) + SupportedGuard.new(*args).run_unless(:not_supported_on, &block) + end +end diff --git a/spec/mspec/lib/mspec/guards/version.rb b/spec/mspec/lib/mspec/guards/version.rb new file mode 100644 index 0000000000..110853e082 --- /dev/null +++ b/spec/mspec/lib/mspec/guards/version.rb @@ -0,0 +1,39 @@ +require 'mspec/utils/deprecate' +require 'mspec/utils/version' +require 'mspec/guards/guard' + +class VersionGuard < SpecGuard + FULL_RUBY_VERSION = SpecVersion.new SpecGuard.ruby_version(:full) + + def initialize(version) + case version + when String + @version = SpecVersion.new version + when Range + MSpec.deprecate "an empty version range end", 'a specific version' if version.end.empty? + a = SpecVersion.new version.begin + b = SpecVersion.new version.end + unless version.exclude_end? + MSpec.deprecate "ruby_version_is with an inclusive range", 'an exclusive range ("2.1"..."2.3")' + end + @version = version.exclude_end? ? a...b : a..b + else + raise "version must be a String or Range but was a #{version.class}" + end + @parameters = [version] + end + + def match? + if Range === @version + @version.include? FULL_RUBY_VERSION + else + FULL_RUBY_VERSION >= @version + end + end +end + +class Object + def ruby_version_is(*args, &block) + VersionGuard.new(*args).run_if(:ruby_version_is, &block) + end +end diff --git a/spec/mspec/lib/mspec/helpers.rb b/spec/mspec/lib/mspec/helpers.rb new file mode 100644 index 0000000000..f2d1c9fb21 --- /dev/null +++ b/spec/mspec/lib/mspec/helpers.rb @@ -0,0 +1,12 @@ +require 'mspec/helpers/argf' +require 'mspec/helpers/argv' +require 'mspec/helpers/datetime' +require 'mspec/helpers/fixture' +require 'mspec/helpers/flunk' +require 'mspec/helpers/fs' +require 'mspec/helpers/io' +require 'mspec/helpers/mock_to_path' +require 'mspec/helpers/numeric' +require 'mspec/helpers/ruby_exe' +require 'mspec/helpers/scratch' +require 'mspec/helpers/tmp' diff --git a/spec/mspec/lib/mspec/helpers/argf.rb b/spec/mspec/lib/mspec/helpers/argf.rb new file mode 100644 index 0000000000..1ba48b9378 --- /dev/null +++ b/spec/mspec/lib/mspec/helpers/argf.rb @@ -0,0 +1,37 @@ +class Object + # Convenience helper for specs using ARGF. + # Set @argf to an instance of ARGF.class with the given +argv+. + # That instance must be used instead of ARGF as ARGF is global + # and it is not always possible to reset its state correctly. + # + # The helper yields to the block and then close + # the files open by the instance. Example: + # + # describe "That" do + # it "does something" do + # argf ['a', 'b'] do + # # do something + # end + # end + # end + def argf(argv) + if argv.empty? or argv.length > 2 + raise "Only 1 or 2 filenames are allowed for the argf helper so files can be properly closed: #{argv.inspect}" + end + @argf ||= nil + raise "Cannot nest calls to the argf helper" if @argf + + @argf = ARGF.class.new(*argv) + @__mspec_saved_argf_file__ = @argf.file + begin + yield + ensure + file1 = @__mspec_saved_argf_file__ + file2 = @argf.file # Either the first file or the second + file1.close if !file1.closed? and file1 != STDIN + file2.close if !file2.closed? and file2 != STDIN + @argf = nil + @__mspec_saved_argf_file__ = nil + end + end +end diff --git a/spec/mspec/lib/mspec/helpers/argv.rb b/spec/mspec/lib/mspec/helpers/argv.rb new file mode 100644 index 0000000000..c8cbbf2ac3 --- /dev/null +++ b/spec/mspec/lib/mspec/helpers/argv.rb @@ -0,0 +1,46 @@ +class Object + # Convenience helper for altering ARGV. Saves the + # value of ARGV and sets it to +args+. If a block + # is given, yields to the block and then restores + # the value of ARGV. The previously saved value of + # ARGV can be restored by passing +:restore+. The + # former is useful in a single spec. The latter is + # useful in before/after actions. For example: + # + # describe "This" do + # before do + # argv ['a', 'b'] + # end + # + # after do + # argv :restore + # end + # + # it "does something" do + # # do something + # end + # end + # + # describe "That" do + # it "does something" do + # argv ['a', 'b'] do + # # do something + # end + # end + # end + def argv(args) + if args == :restore + ARGV.replace(@__mspec_saved_argv__ || []) + else + @__mspec_saved_argv__ = ARGV.dup + ARGV.replace args + if block_given? + begin + yield + ensure + argv :restore + end + end + end + end +end diff --git a/spec/mspec/lib/mspec/helpers/datetime.rb b/spec/mspec/lib/mspec/helpers/datetime.rb new file mode 100644 index 0000000000..4cb57bdaa1 --- /dev/null +++ b/spec/mspec/lib/mspec/helpers/datetime.rb @@ -0,0 +1,51 @@ +class Object + # The new_datetime helper makes writing DateTime specs more simple by + # providing default constructor values and accepting a Hash of only the + # constructor values needed for the particular spec. For example: + # + # new_datetime :hour => 1, :minute => 20 + # + # Possible keys are: + # :year, :month, :day, :hour, :minute, :second, :offset and :sg. + + def new_datetime(opts={}) + require 'date' + + value = { + :year => -4712, + :month => 1, + :day => 1, + :hour => 0, + :minute => 0, + :second => 0, + :offset => 0, + :sg => Date::ITALY + }.merge opts + + DateTime.new value[:year], value[:month], value[:day], value[:hour], + value[:minute], value[:second], value[:offset], value[:sg] + end + + def with_timezone(name, offset = nil, daylight_saving_zone = "") + zone = name.dup + + if offset + # TZ convention is backwards + offset = -offset + + zone += offset.to_s + zone += ":00:00" + end + zone += daylight_saving_zone + + old = ENV["TZ"] + ENV["TZ"] = zone + + begin + yield + ensure + ENV["TZ"] = old + end + end + +end diff --git a/spec/mspec/lib/mspec/helpers/fixture.rb b/spec/mspec/lib/mspec/helpers/fixture.rb new file mode 100644 index 0000000000..718c1b7a94 --- /dev/null +++ b/spec/mspec/lib/mspec/helpers/fixture.rb @@ -0,0 +1,26 @@ +class Object + # Returns the name of a fixture file by adjoining the directory + # of the +file+ argument with "fixtures" and the contents of the + # +args+ array. For example, + # + # +file+ == "some/example_spec.rb" + # + # and + # + # +args+ == ["subdir", "file.txt"] + # + # then the result is the expanded path of + # + # "some/fixtures/subdir/file.txt". + def fixture(file, *args) + path = File.dirname(file) + path = path[0..-7] if path[-7..-1] == "/shared" + fixtures = path[-9..-1] == "/fixtures" ? "" : "fixtures" + if File.respond_to?(:realpath) + path = File.realpath(path) + else + path = File.expand_path(path) + end + File.join(path, fixtures, args) + end +end diff --git a/spec/mspec/lib/mspec/helpers/flunk.rb b/spec/mspec/lib/mspec/helpers/flunk.rb new file mode 100644 index 0000000000..35bd939b85 --- /dev/null +++ b/spec/mspec/lib/mspec/helpers/flunk.rb @@ -0,0 +1,5 @@ +class Object + def flunk(msg="This example is a failure") + SpecExpectation.fail_with "Failed:", msg + end +end diff --git a/spec/mspec/lib/mspec/helpers/fs.rb b/spec/mspec/lib/mspec/helpers/fs.rb new file mode 100644 index 0000000000..ee33f5fec0 --- /dev/null +++ b/spec/mspec/lib/mspec/helpers/fs.rb @@ -0,0 +1,62 @@ +class Object + # Copies a file + def cp(source, dest) + File.open(dest, "w") do |d| + File.open(source, "r") do |s| + while data = s.read(1024) + d.write data + end + end + end + end + + # Creates each directory in path that does not exist. + def mkdir_p(path) + parts = File.expand_path(path).split %r[/|\\] + name = parts.shift + parts.each do |part| + name = File.join name, part + + if File.file? name + raise ArgumentError, "path component of #{path} is a file" + end + + Dir.mkdir name unless File.directory? name + end + end + + # Recursively removes all files and directories in +path+ + # if +path+ is a directory. Removes the file if +path+ is + # a file. + def rm_r(*paths) + paths.each do |path| + path = File.expand_path path + + prefix = SPEC_TEMP_DIR + unless path[0, prefix.size] == prefix + raise ArgumentError, "#{path} is not prefixed by #{prefix}" + end + + # File.symlink? needs to be checked first as + # File.exist? returns false for dangling symlinks + if File.symlink? path + File.unlink path + elsif File.directory? path + Dir.entries(path).each { |x| rm_r "#{path}/#{x}" unless x =~ /^\.\.?$/ } + Dir.rmdir path + elsif File.exist? path + File.delete path + end + end + end + + # Creates a file +name+. Creates the directory for +name+ + # if it does not exist. + def touch(name, mode="w") + mkdir_p File.dirname(name) + + File.open(name, mode) do |f| + yield f if block_given? + end + end +end diff --git a/spec/mspec/lib/mspec/helpers/io.rb b/spec/mspec/lib/mspec/helpers/io.rb new file mode 100644 index 0000000000..83d14441a7 --- /dev/null +++ b/spec/mspec/lib/mspec/helpers/io.rb @@ -0,0 +1,113 @@ +require 'mspec/guards/feature' + +class IOStub + def initialize + @buffer = [] + @output = '' + end + + def write(*str) + self << str.join + end + + def << str + @buffer << str + self + end + + def print(*str) + write(str.join + $\.to_s) + end + + def method_missing(name, *args, &block) + to_s.send(name, *args, &block) + end + + def == other + to_s == other + end + + def =~ other + to_s =~ other + end + + def puts(*str) + if str.empty? + write "\n" + else + write(str.collect { |s| s.to_s.chomp }.concat([nil]).join("\n")) + end + end + + def printf(format, *args) + self << sprintf(format, *args) + end + + def flush + @output += @buffer.join('') + @buffer.clear + self + end + + def to_s + flush + @output + end + + alias_method :to_str, :to_s + + def inspect + to_s.inspect + end +end + +class Object + # Creates a "bare" file descriptor (i.e. one that is not associated + # with any Ruby object). The file descriptor can safely be passed + # to IO.new without creating a Ruby object alias to the fd. + def new_fd(name, mode="w:utf-8") + mode = options_or_mode(mode) + + if mode.kind_of? Hash + if mode.key? :mode + mode = mode[:mode] + else + raise ArgumentError, "new_fd options Hash must include :mode" + end + end + + IO.sysopen name, fmode(mode) + end + + # Creates an IO instance for a temporary file name. The file + # must be deleted. + def new_io(name, mode="w:utf-8") + IO.new new_fd(name, options_or_mode(mode)), options_or_mode(mode) + end + + # This helper simplifies passing file access modes regardless of + # whether the :encoding feature is enabled. Only the access specifier + # itself will be returned if :encoding is not enabled. Otherwise, + # the full mode string will be returned (i.e. the helper is a no-op). + def fmode(mode) + if FeatureGuard.enabled? :encoding + mode + else + mode.split(':').first + end + end + + # This helper simplifies passing file access modes or options regardless of + # whether the :encoding feature is enabled. Only the access specifier itself + # will be returned if :encoding is not enabled. Otherwise, the full mode + # string or option will be returned (i.e. the helper is a no-op). + def options_or_mode(oom) + return fmode(oom) if oom.kind_of? String + + if FeatureGuard.enabled? :encoding + oom + else + fmode(oom[:mode] || "r:utf-8") + end + end +end diff --git a/spec/mspec/lib/mspec/helpers/mock_to_path.rb b/spec/mspec/lib/mspec/helpers/mock_to_path.rb new file mode 100644 index 0000000000..683bb1d9d6 --- /dev/null +++ b/spec/mspec/lib/mspec/helpers/mock_to_path.rb @@ -0,0 +1,8 @@ +class Object + def mock_to_path(path) + # Cannot use our Object#mock here since it conflicts with RSpec + obj = MockObject.new('path') + obj.should_receive(:to_path).and_return(path) + obj + end +end diff --git a/spec/mspec/lib/mspec/helpers/numeric.rb b/spec/mspec/lib/mspec/helpers/numeric.rb new file mode 100644 index 0000000000..ff30cf2b83 --- /dev/null +++ b/spec/mspec/lib/mspec/helpers/numeric.rb @@ -0,0 +1,72 @@ +require 'mspec/guards/platform' + +class Object + def nan_value + 0/0.0 + end + + def infinity_value + 1/0.0 + end + + def bignum_value(plus=0) + 0x8000_0000_0000_0000 + plus + end + + # This is a bit hairy, but we need to be able to write specs that cover the + # boundary between Fixnum and Bignum for operations like Fixnum#<<. Since + # this boundary is implementation-dependent, we use these helpers to write + # specs based on the relationship between values rather than specific + # values. + if PlatformGuard.standard? or PlatformGuard.implementation? :topaz + if PlatformGuard.wordsize? 32 + def fixnum_max + (2**30) - 1 + end + + def fixnum_min + -(2**30) + end + elsif PlatformGuard.wordsize? 64 + def fixnum_max + (2**62) - 1 + end + + def fixnum_min + -(2**62) + end + end + elsif PlatformGuard.implementation? :opal + def fixnum_max + Integer::MAX + end + + def fixnum_min + Integer::MIN + end + elsif PlatformGuard.implementation? :rubinius + def fixnum_max + Fixnum::MAX + end + + def fixnum_min + Fixnum::MIN + end + elsif PlatformGuard.implementation?(:jruby) || PlatformGuard.implementation?(:truffleruby) + def fixnum_max + 9223372036854775807 + end + + def fixnum_min + -9223372036854775808 + end + else + def fixnum_max + raise "unknown implementation for fixnum_max() helper" + end + + def fixnum_min + raise "unknown implementation for fixnum_min() helper" + end + end +end diff --git a/spec/mspec/lib/mspec/helpers/ruby_exe.rb b/spec/mspec/lib/mspec/helpers/ruby_exe.rb new file mode 100644 index 0000000000..a025be6c81 --- /dev/null +++ b/spec/mspec/lib/mspec/helpers/ruby_exe.rb @@ -0,0 +1,178 @@ +require 'mspec/utils/ruby_name' +require 'mspec/guards/platform' +require 'mspec/helpers/tmp' + +# The ruby_exe helper provides a wrapper for invoking the +# same Ruby interpreter with the same falgs as the one running +# the specs and getting the output from running the code. +# If +code+ is a file that exists, it will be run. +# Otherwise, +code+ should be Ruby code that will be run with +# the -e command line option. For example: +# +# ruby_exe('path/to/some/file.rb') +# +# will be executed as +# +# `#{RUBY_EXE} 'path/to/some/file.rb'` +# +# while +# +# ruby_exe('puts "hello, world."') +# +# will be executed as +# +# `#{RUBY_EXE} -e 'puts "hello, world."'` +# +# The ruby_exe helper also accepts an options hash with three +# keys: :options, :args and :env. For example: +# +# ruby_exe('file.rb', :options => "-w", +# :args => "> file.txt", +# :env => { :FOO => "bar" }) +# +# will be executed as +# +# `#{RUBY_EXE} -w #{'file.rb'} > file.txt` +# +# with access to ENV["FOO"] with value "bar". +# +# If +nil+ is passed for the first argument, the command line +# will be built only from the options hash. +# +# The RUBY_EXE constant is setup by mspec automatically +# and is used by ruby_exe and ruby_cmd. The mspec runner script +# will set ENV['RUBY_EXE'] to the name of the executable used +# to invoke the mspec-run script. The value of RUBY_EXE will be +# constructed as follows: +# +# 1. the value of ENV['RUBY_EXE'] +# 2. an explicit value based on RUBY_NAME +# 3. cwd/(RUBY_NAME + $(EXEEXT) || $(exeext) || '') +# 4. $(bindir)/$(RUBY_INSTALL_NAME) +# +# The value will only be used if the file exists and is executable. +# The flags will then be appended to the resulting value. +# +# These 4 ways correspond to the following scenarios: +# +# 1. Using the MSpec runner scripts, the name of the +# executable is explicitly passed by ENV['RUBY_EXE'] +# so there is no ambiguity. +# +# Otherwise, if using RSpec (or something else) +# +# 2. Running the specs while developing an alternative +# Ruby implementation. This explicitly names the +# executable in the development directory based on +# the value of RUBY_NAME, which is probably initialized +# from the value of RUBY_ENGINE. +# 3. Running the specs within the source directory for +# some implementation. (E.g. a local build directory.) +# 4. Running the specs against some installed Ruby +# implementation. +# +# Additionally, the flags passed to mspec +# (with -T on the command line or in the config with set :flags) +# will be appended to RUBY_EXE so that the interpreter +# is always called with those flags. + +class Object + def ruby_exe_options(option) + case option + when :env + ENV['RUBY_EXE'] + when :engine + case RUBY_NAME + when 'rbx' + "bin/rbx" + when 'jruby' + "bin/jruby" + when 'maglev' + "maglev-ruby" + when 'topaz' + "topaz" + when 'ironruby' + "ir" + end + when :name + require 'rbconfig' + bin = RUBY_NAME + (RbConfig::CONFIG['EXEEXT'] || RbConfig::CONFIG['exeext'] || '') + File.join(".", bin) + when :install_name + require 'rbconfig' + bin = RbConfig::CONFIG["RUBY_INSTALL_NAME"] || RbConfig::CONFIG["ruby_install_name"] + bin << (RbConfig::CONFIG['EXEEXT'] || RbConfig::CONFIG['exeext'] || '') + File.join(RbConfig::CONFIG['bindir'], bin) + end + end + + def resolve_ruby_exe + [:env, :engine, :name, :install_name].each do |option| + next unless exe = ruby_exe_options(option) + + if File.file?(exe) and File.executable?(exe) + exe = File.expand_path(exe) + exe = exe.tr('/', '\\') if PlatformGuard.windows? + flags = ENV['RUBY_FLAGS'] + if flags and !flags.empty? + return exe + ' ' + flags + else + return exe + end + end + end + raise Exception, "Unable to find a suitable ruby executable." + end + + def ruby_exe(code, opts = {}) + if opts[:dir] + raise "ruby_exe(..., dir: dir) is no longer supported, use Dir.chdir" + end + + env = opts[:env] || {} + saved_env = {} + env.each do |key, value| + key = key.to_s + saved_env[key] = ENV[key] if ENV.key? key + ENV[key] = value + end + + escape = opts.delete(:escape) + if code and !File.exist?(code) and escape != false + tmpfile = tmp("rubyexe.rb") + File.open(tmpfile, "w") { |f| f.write(code) } + code = tmpfile + end + + begin + platform_is_not :opal do + `#{ruby_cmd(code, opts)}` + end + ensure + saved_env.each { |key, value| ENV[key] = value } + env.keys.each do |key| + key = key.to_s + ENV.delete key unless saved_env.key? key + end + File.delete tmpfile if tmpfile + end + end + + def ruby_cmd(code, opts = {}) + body = code + + if opts[:escape] + raise "escape: true is no longer supported in ruby_cmd, use ruby_exe or a fixture" + end + + if code and !File.exist?(code) + body = "-e #{code.inspect}" + end + + [RUBY_EXE, opts[:options], body, opts[:args]].compact.join(' ') + end + + unless Object.const_defined?(:RUBY_EXE) and RUBY_EXE + RUBY_EXE = resolve_ruby_exe + end +end diff --git a/spec/mspec/lib/mspec/helpers/scratch.rb b/spec/mspec/lib/mspec/helpers/scratch.rb new file mode 100644 index 0000000000..a6b0c02748 --- /dev/null +++ b/spec/mspec/lib/mspec/helpers/scratch.rb @@ -0,0 +1,17 @@ +module ScratchPad + def self.clear + @record = nil + end + + def self.record(arg) + @record = arg + end + + def self.<<(arg) + @record << arg + end + + def self.recorded + @record + end +end diff --git a/spec/mspec/lib/mspec/helpers/tmp.rb b/spec/mspec/lib/mspec/helpers/tmp.rb new file mode 100644 index 0000000000..742eb57fdc --- /dev/null +++ b/spec/mspec/lib/mspec/helpers/tmp.rb @@ -0,0 +1,45 @@ +# Creates a temporary directory in the current working directory +# for temporary files created while running the specs. All specs +# should clean up any temporary files created so that the temp +# directory is empty when the process exits. + +SPEC_TEMP_DIR = File.expand_path(ENV["SPEC_TEMP_DIR"] || "rubyspec_temp") + +SPEC_TEMP_UNIQUIFIER = "0" + +SPEC_TEMP_DIR_PID = Process.pid + +at_exit do + begin + if SPEC_TEMP_DIR_PID == Process.pid + Dir.delete SPEC_TEMP_DIR if File.directory? SPEC_TEMP_DIR + end + rescue SystemCallError + STDERR.puts <<-EOM + +----------------------------------------------------- +The rubyspec temp directory is not empty. Ensure that +all specs are cleaning up temporary files: + #{SPEC_TEMP_DIR} +----------------------------------------------------- + + EOM + rescue Object => e + STDERR.puts "failed to remove spec temp directory" + STDERR.puts e.message + end +end + +class Object + def tmp(name, uniquify=true) + Dir.mkdir SPEC_TEMP_DIR unless Dir.exist? SPEC_TEMP_DIR + + if uniquify and !name.empty? + slash = name.rindex "/" + index = slash ? slash + 1 : 0 + name.insert index, "#{SPEC_TEMP_UNIQUIFIER.succ!}-" + end + + File.join SPEC_TEMP_DIR, name + end +end diff --git a/spec/mspec/lib/mspec/matchers.rb b/spec/mspec/lib/mspec/matchers.rb new file mode 100644 index 0000000000..8eab73198a --- /dev/null +++ b/spec/mspec/lib/mspec/matchers.rb @@ -0,0 +1,35 @@ +require 'mspec/matchers/base' +require 'mspec/matchers/be_an_instance_of' +require 'mspec/matchers/be_ancestor_of' +require 'mspec/matchers/be_close' +require 'mspec/matchers/be_computed_by' +require 'mspec/matchers/be_empty' +require 'mspec/matchers/be_false' +require 'mspec/matchers/be_kind_of' +require 'mspec/matchers/be_nan' +require 'mspec/matchers/be_nil' +require 'mspec/matchers/be_true' +require 'mspec/matchers/be_true_or_false' +require 'mspec/matchers/complain' +require 'mspec/matchers/eql' +require 'mspec/matchers/equal' +require 'mspec/matchers/equal_element' +require 'mspec/matchers/have_constant' +require 'mspec/matchers/have_class_variable' +require 'mspec/matchers/have_instance_method' +require 'mspec/matchers/have_instance_variable' +require 'mspec/matchers/have_method' +require 'mspec/matchers/have_private_instance_method' +require 'mspec/matchers/have_private_method' +require 'mspec/matchers/have_protected_instance_method' +require 'mspec/matchers/have_public_instance_method' +require 'mspec/matchers/have_singleton_method' +require 'mspec/matchers/include' +require 'mspec/matchers/infinity' +require 'mspec/matchers/match_yaml' +require 'mspec/matchers/raise_error' +require 'mspec/matchers/output' +require 'mspec/matchers/output_to_fd' +require 'mspec/matchers/respond_to' +require 'mspec/matchers/signed_zero' +require 'mspec/matchers/block_caller' diff --git a/spec/mspec/lib/mspec/matchers/base.rb b/spec/mspec/lib/mspec/matchers/base.rb new file mode 100644 index 0000000000..30fb1f93dc --- /dev/null +++ b/spec/mspec/lib/mspec/matchers/base.rb @@ -0,0 +1,95 @@ +class SpecPositiveOperatorMatcher + def initialize(actual) + @actual = actual + end + + def ==(expected) + unless @actual == expected + SpecExpectation.fail_with("Expected #{@actual.pretty_inspect}", + "to equal #{expected.pretty_inspect}") + end + end + + def <(expected) + unless @actual < expected + SpecExpectation.fail_with("Expected #{@actual.pretty_inspect}", + "to be less than #{expected.pretty_inspect}") + end + end + + def <=(expected) + unless @actual <= expected + SpecExpectation.fail_with("Expected #{@actual.pretty_inspect}", + "to be less than or equal to #{expected.pretty_inspect}") + end + end + + def >(expected) + unless @actual > expected + SpecExpectation.fail_with("Expected #{@actual.pretty_inspect}", + "to be greater than #{expected.pretty_inspect}") + end + end + + def >=(expected) + unless @actual >= expected + SpecExpectation.fail_with("Expected #{@actual.pretty_inspect}", + "to be greater than or equal to #{expected.pretty_inspect}") + end + end + + def =~(expected) + unless @actual =~ expected + SpecExpectation.fail_with("Expected #{@actual.pretty_inspect}", + "to match #{expected.pretty_inspect}") + end + end +end + +class SpecNegativeOperatorMatcher + def initialize(actual) + @actual = actual + end + + def ==(expected) + if @actual == expected + SpecExpectation.fail_with("Expected #{@actual.pretty_inspect}", + "not to equal #{expected.pretty_inspect}") + end + end + + def <(expected) + if @actual < expected + SpecExpectation.fail_with("Expected #{@actual.pretty_inspect}", + "not to be less than #{expected.pretty_inspect}") + end + end + + def <=(expected) + if @actual <= expected + SpecExpectation.fail_with("Expected #{@actual.pretty_inspect}", + "not to be less than or equal to #{expected.pretty_inspect}") + end + end + + def >(expected) + if @actual > expected + SpecExpectation.fail_with("Expected #{@actual.pretty_inspect}", + "not to be greater than #{expected.pretty_inspect}") + end + end + + def >=(expected) + if @actual >= expected + SpecExpectation.fail_with("Expected #{@actual.pretty_inspect}", + "not to be greater than or equal to #{expected.pretty_inspect}") + end + end + + def =~(expected) + if @actual =~ expected + SpecExpectation.fail_with("Expected #{@actual.pretty_inspect}", + "not to match #{expected.pretty_inspect}") + end + end +end diff --git a/spec/mspec/lib/mspec/matchers/be_an_instance_of.rb b/spec/mspec/lib/mspec/matchers/be_an_instance_of.rb new file mode 100644 index 0000000000..6e31afcddd --- /dev/null +++ b/spec/mspec/lib/mspec/matchers/be_an_instance_of.rb @@ -0,0 +1,26 @@ +class BeAnInstanceOfMatcher + def initialize(expected) + @expected = expected + end + + def matches?(actual) + @actual = actual + @actual.instance_of?(@expected) + end + + def failure_message + ["Expected #{@actual.inspect} (#{@actual.class})", + "to be an instance of #{@expected}"] + end + + def negative_failure_message + ["Expected #{@actual.inspect} (#{@actual.class})", + "not to be an instance of #{@expected}"] + end +end + +class Object + def be_an_instance_of(expected) + BeAnInstanceOfMatcher.new(expected) + end +end diff --git a/spec/mspec/lib/mspec/matchers/be_ancestor_of.rb b/spec/mspec/lib/mspec/matchers/be_ancestor_of.rb new file mode 100644 index 0000000000..792c64089a --- /dev/null +++ b/spec/mspec/lib/mspec/matchers/be_ancestor_of.rb @@ -0,0 +1,24 @@ +class BeAncestorOfMatcher + def initialize(expected) + @expected = expected + end + + def matches?(actual) + @actual = actual + @expected.ancestors.include? @actual + end + + def failure_message + ["Expected #{@actual}", "to be an ancestor of #{@expected}"] + end + + def negative_failure_message + ["Expected #{@actual}", "not to be an ancestor of #{@expected}"] + end +end + +class Object + def be_ancestor_of(expected) + BeAncestorOfMatcher.new(expected) + end +end diff --git a/spec/mspec/lib/mspec/matchers/be_close.rb b/spec/mspec/lib/mspec/matchers/be_close.rb new file mode 100644 index 0000000000..5d79654099 --- /dev/null +++ b/spec/mspec/lib/mspec/matchers/be_close.rb @@ -0,0 +1,27 @@ +TOLERANCE = 0.00003 unless Object.const_defined?(:TOLERANCE) + +class BeCloseMatcher + def initialize(expected, tolerance) + @expected = expected + @tolerance = tolerance + end + + def matches?(actual) + @actual = actual + (@actual - @expected).abs < @tolerance + end + + def failure_message + ["Expected #{@expected}", "to be within +/- #{@tolerance} of #{@actual}"] + end + + def negative_failure_message + ["Expected #{@expected}", "not to be within +/- #{@tolerance} of #{@actual}"] + end +end + +class Object + def be_close(expected, tolerance) + BeCloseMatcher.new(expected, tolerance) + end +end diff --git a/spec/mspec/lib/mspec/matchers/be_computed_by.rb b/spec/mspec/lib/mspec/matchers/be_computed_by.rb new file mode 100644 index 0000000000..c927eb7697 --- /dev/null +++ b/spec/mspec/lib/mspec/matchers/be_computed_by.rb @@ -0,0 +1,37 @@ +class BeComputedByMatcher + def initialize(sym, *args) + @method = sym + @args = args + end + + def matches?(array) + array.each do |line| + @receiver = line.shift + @value = line.pop + @arguments = line + @arguments += @args + @actual = @receiver.send(@method, *@arguments) + return false unless @actual == @value + end + + return true + end + + def method_call + method_call = "#{@receiver.inspect}.#{@method}" + unless @arguments.empty? + method_call = "#{method_call} from #{@arguments.map { |x| x.inspect }.join(", ")}" + end + method_call + end + + def failure_message + ["Expected #{@value.inspect}", "to be computed by #{method_call} (computed #{@actual.inspect} instead)"] + end +end + +class Object + def be_computed_by(sym, *args) + BeComputedByMatcher.new(sym, *args) + end +end diff --git a/spec/mspec/lib/mspec/matchers/be_empty.rb b/spec/mspec/lib/mspec/matchers/be_empty.rb new file mode 100644 index 0000000000..8a401b63fd --- /dev/null +++ b/spec/mspec/lib/mspec/matchers/be_empty.rb @@ -0,0 +1,20 @@ +class BeEmptyMatcher + def matches?(actual) + @actual = actual + @actual.empty? + end + + def failure_message + ["Expected #{@actual.inspect}", "to be empty"] + end + + def negative_failure_message + ["Expected #{@actual.inspect}", "not to be empty"] + end +end + +class Object + def be_empty + BeEmptyMatcher.new + end +end diff --git a/spec/mspec/lib/mspec/matchers/be_false.rb b/spec/mspec/lib/mspec/matchers/be_false.rb new file mode 100644 index 0000000000..0a6e8cfd63 --- /dev/null +++ b/spec/mspec/lib/mspec/matchers/be_false.rb @@ -0,0 +1,20 @@ +class BeFalseMatcher + def matches?(actual) + @actual = actual + @actual == false + end + + def failure_message + ["Expected #{@actual.inspect}", "to be false"] + end + + def negative_failure_message + ["Expected #{@actual.inspect}", "not to be false"] + end +end + +class Object + def be_false + BeFalseMatcher.new + end +end \ No newline at end of file diff --git a/spec/mspec/lib/mspec/matchers/be_kind_of.rb b/spec/mspec/lib/mspec/matchers/be_kind_of.rb new file mode 100644 index 0000000000..a734f6159c --- /dev/null +++ b/spec/mspec/lib/mspec/matchers/be_kind_of.rb @@ -0,0 +1,24 @@ +class BeKindOfMatcher + def initialize(expected) + @expected = expected + end + + def matches?(actual) + @actual = actual + @actual.is_a?(@expected) + end + + def failure_message + ["Expected #{@actual.inspect} (#{@actual.class})", "to be kind of #{@expected}"] + end + + def negative_failure_message + ["Expected #{@actual.inspect} (#{@actual.class})", "not to be kind of #{@expected}"] + end +end + +class Object + def be_kind_of(expected) + BeKindOfMatcher.new(expected) + end +end diff --git a/spec/mspec/lib/mspec/matchers/be_nan.rb b/spec/mspec/lib/mspec/matchers/be_nan.rb new file mode 100644 index 0000000000..aa19391211 --- /dev/null +++ b/spec/mspec/lib/mspec/matchers/be_nan.rb @@ -0,0 +1,20 @@ +class BeNaNMatcher + def matches?(actual) + @actual = actual + @actual.kind_of?(Float) && @actual.nan? + end + + def failure_message + ["Expected #{@actual}", "to be NaN"] + end + + def negative_failure_message + ["Expected #{@actual}", "not to be NaN"] + end +end + +class Object + def be_nan + BeNaNMatcher.new + end +end diff --git a/spec/mspec/lib/mspec/matchers/be_nil.rb b/spec/mspec/lib/mspec/matchers/be_nil.rb new file mode 100644 index 0000000000..ecea6feffa --- /dev/null +++ b/spec/mspec/lib/mspec/matchers/be_nil.rb @@ -0,0 +1,20 @@ +class BeNilMatcher + def matches?(actual) + @actual = actual + @actual.nil? + end + + def failure_message + ["Expected #{@actual.inspect}", "to be nil"] + end + + def negative_failure_message + ["Expected #{@actual.inspect}", "not to be nil"] + end +end + +class Object + def be_nil + BeNilMatcher.new + end +end \ No newline at end of file diff --git a/spec/mspec/lib/mspec/matchers/be_true.rb b/spec/mspec/lib/mspec/matchers/be_true.rb new file mode 100644 index 0000000000..de8e237d35 --- /dev/null +++ b/spec/mspec/lib/mspec/matchers/be_true.rb @@ -0,0 +1,20 @@ +class BeTrueMatcher + def matches?(actual) + @actual = actual + @actual == true + end + + def failure_message + ["Expected #{@actual.inspect}", "to be true"] + end + + def negative_failure_message + ["Expected #{@actual.inspect}", "not to be true"] + end +end + +class Object + def be_true + BeTrueMatcher.new + end +end \ No newline at end of file diff --git a/spec/mspec/lib/mspec/matchers/be_true_or_false.rb b/spec/mspec/lib/mspec/matchers/be_true_or_false.rb new file mode 100644 index 0000000000..b2262779ed --- /dev/null +++ b/spec/mspec/lib/mspec/matchers/be_true_or_false.rb @@ -0,0 +1,20 @@ +class BeTrueOrFalseMatcher + def matches?(actual) + @actual = actual + @actual == true || @actual == false + end + + def failure_message + ["Expected #{@actual.inspect}", "to be true or false"] + end + + def negative_failure_message + ["Expected #{@actual.inspect}", "not to be true or false"] + end +end + +class Object + def be_true_or_false + BeTrueOrFalseMatcher.new + end +end diff --git a/spec/mspec/lib/mspec/matchers/block_caller.rb b/spec/mspec/lib/mspec/matchers/block_caller.rb new file mode 100644 index 0000000000..5451950712 --- /dev/null +++ b/spec/mspec/lib/mspec/matchers/block_caller.rb @@ -0,0 +1,35 @@ +class BlockingMatcher + def matches?(block) + started = false + blocking = true + + thread = Thread.new do + started = true + block.call + + blocking = false + end + + while !started and status = thread.status and status != "sleep" + Thread.pass + end + thread.kill + thread.join + + blocking + end + + def failure_message + ['Expected the given Proc', 'to block the caller'] + end + + def negative_failure_message + ['Expected the given Proc', 'to not block the caller'] + end +end + +class Object + def block_caller(timeout = 0.1) + BlockingMatcher.new + end +end diff --git a/spec/mspec/lib/mspec/matchers/complain.rb b/spec/mspec/lib/mspec/matchers/complain.rb new file mode 100644 index 0000000000..1313215156 --- /dev/null +++ b/spec/mspec/lib/mspec/matchers/complain.rb @@ -0,0 +1,56 @@ +require 'mspec/helpers/io' + +class ComplainMatcher + def initialize(complaint) + @complaint = complaint + end + + def matches?(proc) + @saved_err = $stderr + @stderr = $stderr = IOStub.new + @verbose = $VERBOSE + $VERBOSE = false + + proc.call + + unless @complaint.nil? + case @complaint + when Regexp + return false unless $stderr =~ @complaint + else + return false unless $stderr == @complaint + end + end + + return $stderr.empty? ? false : true + ensure + $VERBOSE = @verbose + $stderr = @saved_err + end + + def failure_message + if @complaint.nil? + ["Expected a warning", "but received none"] + elsif @complaint.kind_of? Regexp + ["Expected warning to match: #{@complaint.inspect}", "but got: #{@stderr.chomp.inspect}"] + else + ["Expected warning: #{@complaint.inspect}", "but got: #{@stderr.chomp.inspect}"] + end + end + + def negative_failure_message + if @complaint.nil? + ["Unexpected warning: ", @stderr.chomp.inspect] + elsif @complaint.kind_of? Regexp + ["Expected warning not to match: #{@complaint.inspect}", "but got: #{@stderr.chomp.inspect}"] + else + ["Expected warning: #{@complaint.inspect}", "but got: #{@stderr.chomp.inspect}"] + end + end +end + +class Object + def complain(complaint=nil) + ComplainMatcher.new(complaint) + end +end diff --git a/spec/mspec/lib/mspec/matchers/eql.rb b/spec/mspec/lib/mspec/matchers/eql.rb new file mode 100644 index 0000000000..82117d862c --- /dev/null +++ b/spec/mspec/lib/mspec/matchers/eql.rb @@ -0,0 +1,26 @@ +class EqlMatcher + def initialize(expected) + @expected = expected + end + + def matches?(actual) + @actual = actual + @actual.eql?(@expected) + end + + def failure_message + ["Expected #{@actual.pretty_inspect}", + "to have same value and type as #{@expected.pretty_inspect}"] + end + + def negative_failure_message + ["Expected #{@actual.pretty_inspect}", + "not to have same value or type as #{@expected.pretty_inspect}"] + end +end + +class Object + def eql(expected) + EqlMatcher.new(expected) + end +end diff --git a/spec/mspec/lib/mspec/matchers/equal.rb b/spec/mspec/lib/mspec/matchers/equal.rb new file mode 100644 index 0000000000..ee6431fd4f --- /dev/null +++ b/spec/mspec/lib/mspec/matchers/equal.rb @@ -0,0 +1,26 @@ +class EqualMatcher + def initialize(expected) + @expected = expected + end + + def matches?(actual) + @actual = actual + @actual.equal?(@expected) + end + + def failure_message + ["Expected #{@actual.pretty_inspect}", + "to be identical to #{@expected.pretty_inspect}"] + end + + def negative_failure_message + ["Expected #{@actual.pretty_inspect}", + "not to be identical to #{@expected.pretty_inspect}"] + end +end + +class Object + def equal(expected) + EqualMatcher.new(expected) + end +end diff --git a/spec/mspec/lib/mspec/matchers/equal_element.rb b/spec/mspec/lib/mspec/matchers/equal_element.rb new file mode 100644 index 0000000000..8d032fd088 --- /dev/null +++ b/spec/mspec/lib/mspec/matchers/equal_element.rb @@ -0,0 +1,78 @@ +class EqualElementMatcher + def initialize(element, attributes = nil, content = nil, options = {}) + @element = element + @attributes = attributes + @content = content + @options = options + end + + def matches?(actual) + @actual = actual + + matched = true + + if @options[:not_closed] + matched &&= actual =~ /^#{Regexp.quote("<" + @element)}.*#{Regexp.quote(">" + (@content || ''))}$/ + else + matched &&= actual =~ /^#{Regexp.quote("<" + @element)}/ + matched &&= actual =~ /#{Regexp.quote("")}$/ + matched &&= actual =~ /#{Regexp.quote(">" + @content + ")/).size == 1) + else + matched &&= (actual.scan(%Q{ #{key}="#{value}"}).size == 1) + end + end + end + end + + !!matched + end + + def failure_message + ["Expected #{@actual.pretty_inspect}", + "to be a '#{@element}' element with #{attributes_for_failure_message} and #{content_for_failure_message}"] + end + + def negative_failure_message + ["Expected #{@actual.pretty_inspect}", + "not to be a '#{@element}' element with #{attributes_for_failure_message} and #{content_for_failure_message}"] + end + + def attributes_for_failure_message + if @attributes + if @attributes.empty? + "no attributes" + else + @attributes.inject([]) { |memo, n| memo << %Q{#{n[0]}="#{n[1]}"} }.join(" ") + end + else + "any attributes" + end + end + + def content_for_failure_message + if @content + if @content.empty? + "no content" + else + "#{@content.inspect} as content" + end + else + "any content" + end + end +end + +class Object + def equal_element(*args) + EqualElementMatcher.new(*args) + end +end \ No newline at end of file diff --git a/spec/mspec/lib/mspec/matchers/have_class_variable.rb b/spec/mspec/lib/mspec/matchers/have_class_variable.rb new file mode 100644 index 0000000000..45cd0b5ae1 --- /dev/null +++ b/spec/mspec/lib/mspec/matchers/have_class_variable.rb @@ -0,0 +1,12 @@ +require 'mspec/matchers/variable' + +class HaveClassVariableMatcher < VariableMatcher + self.variables_method = :class_variables + self.description = 'class variable' +end + +class Object + def have_class_variable(variable) + HaveClassVariableMatcher.new(variable) + end +end \ No newline at end of file diff --git a/spec/mspec/lib/mspec/matchers/have_constant.rb b/spec/mspec/lib/mspec/matchers/have_constant.rb new file mode 100644 index 0000000000..df95219e53 --- /dev/null +++ b/spec/mspec/lib/mspec/matchers/have_constant.rb @@ -0,0 +1,12 @@ +require 'mspec/matchers/variable' + +class HaveConstantMatcher < VariableMatcher + self.variables_method = :constants + self.description = 'constant' +end + +class Object + def have_constant(variable) + HaveConstantMatcher.new(variable) + end +end diff --git a/spec/mspec/lib/mspec/matchers/have_instance_method.rb b/spec/mspec/lib/mspec/matchers/have_instance_method.rb new file mode 100644 index 0000000000..00dcbd39eb --- /dev/null +++ b/spec/mspec/lib/mspec/matchers/have_instance_method.rb @@ -0,0 +1,24 @@ +require 'mspec/matchers/method' + +class HaveInstanceMethodMatcher < MethodMatcher + def matches?(mod) + @mod = mod + mod.instance_methods(@include_super).include? @method + end + + def failure_message + ["Expected #{@mod} to have instance method '#{@method.to_s}'", + "but it does not"] + end + + def negative_failure_message + ["Expected #{@mod} NOT to have instance method '#{@method.to_s}'", + "but it does"] + end +end + +class Object + def have_instance_method(method, include_super=true) + HaveInstanceMethodMatcher.new method, include_super + end +end diff --git a/spec/mspec/lib/mspec/matchers/have_instance_variable.rb b/spec/mspec/lib/mspec/matchers/have_instance_variable.rb new file mode 100644 index 0000000000..e83eb9408c --- /dev/null +++ b/spec/mspec/lib/mspec/matchers/have_instance_variable.rb @@ -0,0 +1,12 @@ +require 'mspec/matchers/variable' + +class HaveInstanceVariableMatcher < VariableMatcher + self.variables_method = :instance_variables + self.description = 'instance variable' +end + +class Object + def have_instance_variable(variable) + HaveInstanceVariableMatcher.new(variable) + end +end \ No newline at end of file diff --git a/spec/mspec/lib/mspec/matchers/have_method.rb b/spec/mspec/lib/mspec/matchers/have_method.rb new file mode 100644 index 0000000000..2fc3e66f69 --- /dev/null +++ b/spec/mspec/lib/mspec/matchers/have_method.rb @@ -0,0 +1,24 @@ +require 'mspec/matchers/method' + +class HaveMethodMatcher < MethodMatcher + def matches?(mod) + @mod = mod + @mod.methods(@include_super).include? @method + end + + def failure_message + ["Expected #{@mod} to have method '#{@method.to_s}'", + "but it does not"] + end + + def negative_failure_message + ["Expected #{@mod} NOT to have method '#{@method.to_s}'", + "but it does"] + end +end + +class Object + def have_method(method, include_super=true) + HaveMethodMatcher.new method, include_super + end +end diff --git a/spec/mspec/lib/mspec/matchers/have_private_instance_method.rb b/spec/mspec/lib/mspec/matchers/have_private_instance_method.rb new file mode 100644 index 0000000000..87d9767a69 --- /dev/null +++ b/spec/mspec/lib/mspec/matchers/have_private_instance_method.rb @@ -0,0 +1,24 @@ +require 'mspec/matchers/method' + +class HavePrivateInstanceMethodMatcher < MethodMatcher + def matches?(mod) + @mod = mod + mod.private_instance_methods(@include_super).include? @method + end + + def failure_message + ["Expected #{@mod} to have private instance method '#{@method.to_s}'", + "but it does not"] + end + + def negative_failure_message + ["Expected #{@mod} NOT to have private instance method '#{@method.to_s}'", + "but it does"] + end +end + +class Object + def have_private_instance_method(method, include_super=true) + HavePrivateInstanceMethodMatcher.new method, include_super + end +end diff --git a/spec/mspec/lib/mspec/matchers/have_private_method.rb b/spec/mspec/lib/mspec/matchers/have_private_method.rb new file mode 100644 index 0000000000..d99d4ccb7f --- /dev/null +++ b/spec/mspec/lib/mspec/matchers/have_private_method.rb @@ -0,0 +1,24 @@ +require 'mspec/matchers/method' + +class HavePrivateMethodMatcher < MethodMatcher + def matches?(mod) + @mod = mod + mod.private_methods(@include_super).include? @method + end + + def failure_message + ["Expected #{@mod} to have private method '#{@method.to_s}'", + "but it does not"] + end + + def negative_failure_message + ["Expected #{@mod} NOT to have private method '#{@method.to_s}'", + "but it does"] + end +end + +class Object + def have_private_method(method, include_super=true) + HavePrivateMethodMatcher.new method, include_super + end +end diff --git a/spec/mspec/lib/mspec/matchers/have_protected_instance_method.rb b/spec/mspec/lib/mspec/matchers/have_protected_instance_method.rb new file mode 100644 index 0000000000..92f38e9acb --- /dev/null +++ b/spec/mspec/lib/mspec/matchers/have_protected_instance_method.rb @@ -0,0 +1,24 @@ +require 'mspec/matchers/method' + +class HaveProtectedInstanceMethodMatcher < MethodMatcher + def matches?(mod) + @mod = mod + mod.protected_instance_methods(@include_super).include? @method + end + + def failure_message + ["Expected #{@mod} to have protected instance method '#{@method.to_s}'", + "but it does not"] + end + + def negative_failure_message + ["Expected #{@mod} NOT to have protected instance method '#{@method.to_s}'", + "but it does"] + end +end + +class Object + def have_protected_instance_method(method, include_super=true) + HaveProtectedInstanceMethodMatcher.new method, include_super + end +end diff --git a/spec/mspec/lib/mspec/matchers/have_public_instance_method.rb b/spec/mspec/lib/mspec/matchers/have_public_instance_method.rb new file mode 100644 index 0000000000..035547d28f --- /dev/null +++ b/spec/mspec/lib/mspec/matchers/have_public_instance_method.rb @@ -0,0 +1,24 @@ +require 'mspec/matchers/method' + +class HavePublicInstanceMethodMatcher < MethodMatcher + def matches?(mod) + @mod = mod + mod.public_instance_methods(@include_super).include? @method + end + + def failure_message + ["Expected #{@mod} to have public instance method '#{@method.to_s}'", + "but it does not"] + end + + def negative_failure_message + ["Expected #{@mod} NOT to have public instance method '#{@method.to_s}'", + "but it does"] + end +end + +class Object + def have_public_instance_method(method, include_super=true) + HavePublicInstanceMethodMatcher.new method, include_super + end +end diff --git a/spec/mspec/lib/mspec/matchers/have_singleton_method.rb b/spec/mspec/lib/mspec/matchers/have_singleton_method.rb new file mode 100644 index 0000000000..5f3acb84e2 --- /dev/null +++ b/spec/mspec/lib/mspec/matchers/have_singleton_method.rb @@ -0,0 +1,24 @@ +require 'mspec/matchers/method' + +class HaveSingletonMethodMatcher < MethodMatcher + def matches?(obj) + @obj = obj + obj.singleton_methods(@include_super).include? @method + end + + def failure_message + ["Expected #{@obj} to have singleton method '#{@method.to_s}'", + "but it does not"] + end + + def negative_failure_message + ["Expected #{@obj} NOT to have singleton method '#{@method.to_s}'", + "but it does"] + end +end + +class Object + def have_singleton_method(method, include_super=true) + HaveSingletonMethodMatcher.new method, include_super + end +end diff --git a/spec/mspec/lib/mspec/matchers/include.rb b/spec/mspec/lib/mspec/matchers/include.rb new file mode 100644 index 0000000000..b4e54158d1 --- /dev/null +++ b/spec/mspec/lib/mspec/matchers/include.rb @@ -0,0 +1,32 @@ +class IncludeMatcher + def initialize(*expected) + @expected = expected + end + + def matches?(actual) + @actual = actual + @expected.each do |e| + @element = e + unless @actual.include?(e) + return false + end + end + return true + end + + def failure_message + ["Expected #{@actual.inspect}", "to include #{@element.inspect}"] + end + + def negative_failure_message + ["Expected #{@actual.inspect}", "not to include #{@element.inspect}"] + end +end + +# Cannot override #include at the toplevel in MRI +module MSpec + def include(*expected) + IncludeMatcher.new(*expected) + end + module_function :include +end diff --git a/spec/mspec/lib/mspec/matchers/infinity.rb b/spec/mspec/lib/mspec/matchers/infinity.rb new file mode 100644 index 0000000000..0949fd47eb --- /dev/null +++ b/spec/mspec/lib/mspec/matchers/infinity.rb @@ -0,0 +1,28 @@ +class InfinityMatcher + def initialize(expected_sign) + @expected_sign = expected_sign + end + + def matches?(actual) + @actual = actual + @actual.kind_of?(Float) && @actual.infinite? == @expected_sign + end + + def failure_message + ["Expected #{@actual}", "to be #{"-" if @expected_sign == -1}Infinity"] + end + + def negative_failure_message + ["Expected #{@actual}", "not to be #{"-" if @expected_sign == -1}Infinity"] + end +end + +class Object + def be_positive_infinity + InfinityMatcher.new(1) + end + + def be_negative_infinity + InfinityMatcher.new(-1) + end +end diff --git a/spec/mspec/lib/mspec/matchers/match_yaml.rb b/spec/mspec/lib/mspec/matchers/match_yaml.rb new file mode 100644 index 0000000000..542dece2b4 --- /dev/null +++ b/spec/mspec/lib/mspec/matchers/match_yaml.rb @@ -0,0 +1,47 @@ +class MatchYAMLMatcher + + def initialize(expected) + if valid_yaml?(expected) + @expected = expected + else + @expected = expected.to_yaml + end + end + + def matches?(actual) + @actual = actual + clean_yaml(@actual) == clean_yaml(@expected) + end + + def failure_message + ["Expected #{@actual.inspect}", " to match #{@expected.inspect}"] + end + + def negative_failure_message + ["Expected #{@actual.inspect}", " to match #{@expected.inspect}"] + end + + protected + + def clean_yaml(yaml) + yaml.gsub(/([^-]|^---)\s+\n/, "\\1\n").sub(/\n\.\.\.\n$/, "\n") + end + + def valid_yaml?(obj) + require 'yaml' + begin + YAML.load(obj) + rescue + false + else + true + end + end +end + +class Object + def match_yaml(expected) + MatchYAMLMatcher.new(expected) + end +end + diff --git a/spec/mspec/lib/mspec/matchers/method.rb b/spec/mspec/lib/mspec/matchers/method.rb new file mode 100644 index 0000000000..e8cdfa62ff --- /dev/null +++ b/spec/mspec/lib/mspec/matchers/method.rb @@ -0,0 +1,10 @@ +class MethodMatcher + def initialize(method, include_super=true) + @include_super = include_super + @method = method.to_sym + end + + def matches?(mod) + raise Exception, "define #matches? in the subclass" + end +end diff --git a/spec/mspec/lib/mspec/matchers/output.rb b/spec/mspec/lib/mspec/matchers/output.rb new file mode 100644 index 0000000000..551e7506cf --- /dev/null +++ b/spec/mspec/lib/mspec/matchers/output.rb @@ -0,0 +1,67 @@ +require 'mspec/helpers/io' + +class OutputMatcher + def initialize(stdout, stderr) + @out = stdout + @err = stderr + end + + def matches?(proc) + @saved_out = $stdout + @saved_err = $stderr + @stdout = $stdout = IOStub.new + @stderr = $stderr = IOStub.new + + proc.call + + unless @out.nil? + case @out + when Regexp + return false unless $stdout =~ @out + else + return false unless $stdout == @out + end + end + + unless @err.nil? + case @err + when Regexp + return false unless $stderr =~ @err + else + return false unless $stderr == @err + end + end + + return true + ensure + $stdout = @saved_out + $stderr = @saved_err + end + + def failure_message + expected_out = "\n" + actual_out = "\n" + unless @out.nil? + expected_out += " $stdout: #{@out.inspect}\n" + actual_out += " $stdout: #{@stdout.inspect}\n" + end + unless @err.nil? + expected_out += " $stderr: #{@err.inspect}\n" + actual_out += " $stderr: #{@stderr.inspect}\n" + end + ["Expected:#{expected_out}", " got:#{actual_out}"] + end + + def negative_failure_message + out = "" + out += " $stdout: #{@stdout.chomp.dump}\n" unless @out.nil? + out += " $stderr: #{@stderr.chomp.dump}\n" unless @err.nil? + ["Expected output not to be:\n", out] + end +end + +class Object + def output(stdout=nil, stderr=nil) + OutputMatcher.new(stdout, stderr) + end +end diff --git a/spec/mspec/lib/mspec/matchers/output_to_fd.rb b/spec/mspec/lib/mspec/matchers/output_to_fd.rb new file mode 100644 index 0000000000..5daaf5545c --- /dev/null +++ b/spec/mspec/lib/mspec/matchers/output_to_fd.rb @@ -0,0 +1,71 @@ +require 'mspec/helpers/tmp' + +# Lower-level output speccing mechanism for a single +# output stream. Unlike OutputMatcher which provides +# methods to capture the output, we actually replace +# the FD itself so that there is no reliance on a +# certain method being used. +class OutputToFDMatcher + def initialize(expected, to) + @to, @expected = to, expected + + case @to + when STDOUT + @to_name = "STDOUT" + when STDERR + @to_name = "STDERR" + when IO + @to_name = @to.object_id.to_s + else + raise ArgumentError, "#{@to.inspect} is not a supported output target" + end + end + + def with_tmp + path = tmp("mspec_output_to_#{$$}_#{Time.now.to_i}") + File.open(path, 'w+') { |io| + yield(io) + } + ensure + File.delete path if path + end + + def matches?(block) + old_to = @to.dup + with_tmp do |out| + # Replacing with a file handle so that Readline etc. work + @to.reopen out + begin + block.call + ensure + @to.reopen old_to + old_to.close + end + + out.rewind + @actual = out.read + + case @expected + when Regexp + !(@actual =~ @expected).nil? + else + @actual == @expected + end + end + end + + def failure_message() + ["Expected (#{@to_name}): #{@expected.inspect}\n", + "#{'but got'.rjust(@to_name.length + 10)}: #{@actual.inspect}\nBacktrace"] + end + + def negative_failure_message() + ["Expected output (#{@to_name}) to NOT be:\n", @actual.inspect] + end +end + +class Object + def output_to_fd(what, where = STDOUT) + OutputToFDMatcher.new what, where + end +end diff --git a/spec/mspec/lib/mspec/matchers/raise_error.rb b/spec/mspec/lib/mspec/matchers/raise_error.rb new file mode 100644 index 0000000000..a5d6e01ec9 --- /dev/null +++ b/spec/mspec/lib/mspec/matchers/raise_error.rb @@ -0,0 +1,79 @@ +require 'mspec/utils/deprecate' + +class RaiseErrorMatcher + def initialize(exception, message, &block) + @exception = exception + @message = message + @block = block + end + + def matches?(proc) + @result = proc.call + return false + rescue Exception => @actual + if matching_exception?(@actual) + return true + else + raise @actual + end + end + + def matching_exception?(exc) + return false unless @exception === exc + if @message then + case @message + when String + return false if @message != exc.message + when Regexp + return false if @message !~ exc.message + end + end + + # The block has its own expectations and will throw an exception if it fails + @block[exc] if @block + + return true + end + + def exception_class_and_message(exception_class, message) + if message + "#{exception_class} (#{message})" + else + "#{exception_class}" + end + end + + def format_expected_exception + exception_class_and_message(@exception, @message) + end + + def format_exception(exception) + exception_class_and_message(exception.class, exception.message) + end + + def failure_message + message = ["Expected #{format_expected_exception}"] + + if @actual then + message << "but got #{format_exception(@actual)}" + else + message << "but no exception was raised (#{@result.pretty_inspect.chomp} was returned)" + end + + message + end + + def negative_failure_message + message = ["Expected to not get #{format_expected_exception}", ""] + unless @actual.class == @exception + message[1] = "but got #{format_exception(@actual)}" + end + message + end +end + +class Object + def raise_error(exception=Exception, message=nil, &block) + RaiseErrorMatcher.new(exception, message, &block) + end +end diff --git a/spec/mspec/lib/mspec/matchers/respond_to.rb b/spec/mspec/lib/mspec/matchers/respond_to.rb new file mode 100644 index 0000000000..2aa3ab14d1 --- /dev/null +++ b/spec/mspec/lib/mspec/matchers/respond_to.rb @@ -0,0 +1,24 @@ +class RespondToMatcher + def initialize(expected) + @expected = expected + end + + def matches?(actual) + @actual = actual + @actual.respond_to?(@expected) + end + + def failure_message + ["Expected #{@actual.inspect} (#{@actual.class})", "to respond to #{@expected}"] + end + + def negative_failure_message + ["Expected #{@actual.inspect} (#{@actual.class})", "not to respond to #{@expected}"] + end +end + +class Object + def respond_to(expected) + RespondToMatcher.new(expected) + end +end diff --git a/spec/mspec/lib/mspec/matchers/signed_zero.rb b/spec/mspec/lib/mspec/matchers/signed_zero.rb new file mode 100644 index 0000000000..3fd1472fc8 --- /dev/null +++ b/spec/mspec/lib/mspec/matchers/signed_zero.rb @@ -0,0 +1,28 @@ +class SignedZeroMatcher + def initialize(expected_sign) + @expected_sign = expected_sign + end + + def matches?(actual) + @actual = actual + (1.0/actual).infinite? == @expected_sign + end + + def failure_message + ["Expected #{@actual}", "to be #{"-" if @expected_sign == -1}0.0"] + end + + def negative_failure_message + ["Expected #{@actual}", "not to be #{"-" if @expected_sign == -1}0.0"] + end +end + +class Object + def be_positive_zero + SignedZeroMatcher.new(1) + end + + def be_negative_zero + SignedZeroMatcher.new(-1) + end +end diff --git a/spec/mspec/lib/mspec/matchers/variable.rb b/spec/mspec/lib/mspec/matchers/variable.rb new file mode 100644 index 0000000000..4d801ea337 --- /dev/null +++ b/spec/mspec/lib/mspec/matchers/variable.rb @@ -0,0 +1,24 @@ +class VariableMatcher + class << self + attr_accessor :variables_method, :description + end + + def initialize(variable) + @variable = variable.to_sym + end + + def matches?(object) + @object = object + @object.send(self.class.variables_method).include? @variable + end + + def failure_message + ["Expected #{@object} to have #{self.class.description} '#{@variable}'", + "but it does not"] + end + + def negative_failure_message + ["Expected #{@object} NOT to have #{self.class.description} '#{@variable}'", + "but it does"] + end +end diff --git a/spec/mspec/lib/mspec/mocks.rb b/spec/mspec/lib/mspec/mocks.rb new file mode 100644 index 0000000000..6a029c7b53 --- /dev/null +++ b/spec/mspec/lib/mspec/mocks.rb @@ -0,0 +1,3 @@ +require 'mspec/mocks/mock' +require 'mspec/mocks/proxy' +require 'mspec/mocks/object' diff --git a/spec/mspec/lib/mspec/mocks/mock.rb b/spec/mspec/lib/mspec/mocks/mock.rb new file mode 100644 index 0000000000..1557f2008e --- /dev/null +++ b/spec/mspec/lib/mspec/mocks/mock.rb @@ -0,0 +1,197 @@ +require 'mspec/expectations/expectations' + +class Object + alias_method :__mspec_object_id__, :object_id +end + +module Mock + def self.reset + @mocks = @stubs = @objects = nil + end + + def self.objects + @objects ||= {} + end + + def self.mocks + @mocks ||= Hash.new { |h,k| h[k] = [] } + end + + def self.stubs + @stubs ||= Hash.new { |h,k| h[k] = [] } + end + + def self.replaced_name(obj, sym) + :"__mspec_#{obj.__mspec_object_id__}_#{sym}__" + end + + def self.replaced_key(obj, sym) + [replaced_name(obj, sym), sym] + end + + def self.has_key?(keys, sym) + !!keys.find { |k| k.first == sym } + end + + def self.replaced?(sym) + has_key?(mocks.keys, sym) or has_key?(stubs.keys, sym) + end + + def self.clear_replaced(key) + mocks.delete key + stubs.delete key + end + + def self.mock_respond_to?(obj, sym, include_private = false) + name = replaced_name(obj, :respond_to?) + if replaced? name + obj.__send__ name, sym, include_private + else + obj.respond_to? sym, include_private + end + end + + def self.install_method(obj, sym, type=nil) + meta = obj.singleton_class + + key = replaced_key obj, sym + sym = sym.to_sym + + if (sym == :respond_to? or mock_respond_to?(obj, sym, true)) and !replaced?(key.first) + meta.__send__ :alias_method, key.first, sym + end + + meta.class_eval { + define_method(sym) do |*args, &block| + Mock.verify_call self, sym, *args, &block + end + } + + proxy = MockProxy.new type + + if proxy.mock? + MSpec.expectation + MSpec.actions :expectation, MSpec.current.state + end + + if proxy.stub? + stubs[key].unshift proxy + else + mocks[key] << proxy + end + objects[key] = obj + + proxy + end + + def self.name_or_inspect(obj) + obj.instance_variable_get(:@name) || obj.inspect + end + + def self.verify_count + mocks.each do |key, proxies| + obj = objects[key] + proxies.each do |proxy| + qualifier, count = proxy.count + pass = case qualifier + when :at_least + proxy.calls >= count + when :at_most + proxy.calls <= count + when :exactly + proxy.calls == count + when :any_number_of_times + true + else + false + end + unless pass + SpecExpectation.fail_with( + "Mock '#{name_or_inspect obj}' expected to receive '#{key.last}' " + \ + "#{qualifier.to_s.sub('_', ' ')} #{count} times", + "but received it #{proxy.calls} times") + end + end + end + end + + def self.verify_call(obj, sym, *args, &block) + compare = *args + compare = compare.first if compare.length <= 1 + + key = replaced_key obj, sym + [mocks, stubs].each do |proxies| + proxies[key].each do |proxy| + pass = case proxy.arguments + when :any_args + true + when :no_args + compare.nil? + else + proxy.arguments == compare + end + + if proxy.yielding? + if block + proxy.yielding.each do |args_to_yield| + if block.arity == -1 || block.arity == args_to_yield.size + block.call(*args_to_yield) + else + SpecExpectation.fail_with( + "Mock '#{name_or_inspect obj}' asked to yield " + \ + "|#{proxy.yielding.join(', ')}| on #{sym}\n", + "but a block with arity #{block.arity} was passed") + end + end + else + SpecExpectation.fail_with( + "Mock '#{name_or_inspect obj}' asked to yield " + \ + "|[#{proxy.yielding.join('], [')}]| on #{sym}\n", + "but no block was passed") + end + end + + if pass + proxy.called + + if proxy.raising? + raise proxy.raising + else + return proxy.returning + end + end + end + end + + if sym.to_sym == :respond_to? + mock_respond_to? obj, compare + else + SpecExpectation.fail_with("Mock '#{name_or_inspect obj}': method #{sym}\n", + "called with unexpected arguments (#{Array(compare).join(' ')})") + end + end + + def self.cleanup + objects.each do |key, obj| + if obj.kind_of? MockIntObject + clear_replaced key + next + end + + replaced = key.first + sym = key.last + meta = obj.singleton_class + + if mock_respond_to? obj, replaced, true + meta.__send__ :alias_method, sym, replaced + meta.__send__ :remove_method, replaced + else + meta.__send__ :remove_method, sym + end + + clear_replaced key + end + ensure + reset + end +end diff --git a/spec/mspec/lib/mspec/mocks/object.rb b/spec/mspec/lib/mspec/mocks/object.rb new file mode 100644 index 0000000000..f4652a4671 --- /dev/null +++ b/spec/mspec/lib/mspec/mocks/object.rb @@ -0,0 +1,28 @@ +require 'mspec/mocks/proxy' + +class Object + def stub!(sym) + Mock.install_method self, sym, :stub + end + + def should_receive(sym) + Mock.install_method self, sym + end + + def should_not_receive(sym) + proxy = Mock.install_method self, sym + proxy.exactly(0).times + end + + def mock(name, options={}) + MockObject.new name, options + end + + def mock_int(val) + MockIntObject.new(val) + end + + def mock_numeric(name, options={}) + NumericMockObject.new name, options + end +end diff --git a/spec/mspec/lib/mspec/mocks/proxy.rb b/spec/mspec/lib/mspec/mocks/proxy.rb new file mode 100644 index 0000000000..f5acc89d62 --- /dev/null +++ b/spec/mspec/lib/mspec/mocks/proxy.rb @@ -0,0 +1,186 @@ +class MockObject + def initialize(name, options={}) + @name = name + @null = options[:null_object] + end + + def method_missing(sym, *args, &block) + @null ? self : super + end + private :method_missing +end + +class NumericMockObject < Numeric + def initialize(name, options={}) + @name = name + @null = options[:null_object] + end + + def method_missing(sym, *args, &block) + @null ? self : super + end + + def singleton_method_added(val) + end +end + +class MockIntObject + def initialize(val) + @value = val + @calls = 0 + + key = [self, :to_int] + + Mock.objects[key] = self + Mock.mocks[key] << self + end + + attr_reader :calls + + def to_int + @calls += 1 + @value.to_int + end + + def count + [:at_least, 1] + end +end + +class MockProxy + attr_reader :raising, :yielding + + def initialize(type=nil) + @multiple_returns = nil + @returning = nil + @raising = nil + @yielding = [] + @arguments = :any_args + @type = type || :mock + end + + def mock? + @type == :mock + end + + def stub? + @type == :stub + end + + def count + @count ||= mock? ? [:exactly, 1] : [:any_number_of_times, 0] + end + + def arguments + @arguments + end + + def returning + if @multiple_returns + if @returning.size == 1 + @multiple_returns = false + return @returning = @returning.shift + end + return @returning.shift + end + @returning + end + + def times + self + end + + def calls + @calls ||= 0 + end + + def called + @calls = calls + 1 + end + + def exactly(n) + @count = [:exactly, n_times(n)] + self + end + + def at_least(n) + @count = [:at_least, n_times(n)] + self + end + + def at_most(n) + @count = [:at_most, n_times(n)] + self + end + + def once + exactly 1 + end + + def twice + exactly 2 + end + + def any_number_of_times + @count = [:any_number_of_times, 0] + self + end + + def with(*args) + raise ArgumentError, "you must specify the expected arguments" if args.empty? + if args.length == 1 + @arguments = args.first + else + @arguments = args + end + self + end + + def and_return(*args) + case args.size + when 0 + @returning = nil + when 1 + @returning = args[0] + else + @multiple_returns = true + @returning = args + count[1] = args.size if count[1] < args.size + end + self + end + + def and_raise(exception) + if exception.kind_of? String + @raising = RuntimeError.new exception + else + @raising = exception + end + end + + def raising? + @raising != nil + end + + def and_yield(*args) + @yielding << args + self + end + + def yielding? + !@yielding.empty? + end + + private + + def n_times(n) + case n + when :once + 1 + when :twice + 2 + else + Integer n + end + end +end diff --git a/spec/mspec/lib/mspec/runner.rb b/spec/mspec/lib/mspec/runner.rb new file mode 100644 index 0000000000..df57b9f69b --- /dev/null +++ b/spec/mspec/lib/mspec/runner.rb @@ -0,0 +1,12 @@ +require 'mspec/mocks' +require 'mspec/runner/mspec' +require 'mspec/runner/context' +require 'mspec/runner/evaluate' +require 'mspec/runner/example' +require 'mspec/runner/exception' +require 'mspec/runner/object' +require 'mspec/runner/formatters' +require 'mspec/runner/actions' +require 'mspec/runner/filters' +require 'mspec/runner/shared' +require 'mspec/runner/tag' diff --git a/spec/mspec/lib/mspec/runner/actions.rb b/spec/mspec/lib/mspec/runner/actions.rb new file mode 100644 index 0000000000..0a5a05fbd1 --- /dev/null +++ b/spec/mspec/lib/mspec/runner/actions.rb @@ -0,0 +1,6 @@ +require 'mspec/runner/actions/tally' +require 'mspec/runner/actions/timer' +require 'mspec/runner/actions/filter' +require 'mspec/runner/actions/tag' +require 'mspec/runner/actions/taglist' +require 'mspec/runner/actions/tagpurge' diff --git a/spec/mspec/lib/mspec/runner/actions/filter.rb b/spec/mspec/lib/mspec/runner/actions/filter.rb new file mode 100644 index 0000000000..35899c8dc8 --- /dev/null +++ b/spec/mspec/lib/mspec/runner/actions/filter.rb @@ -0,0 +1,40 @@ +require 'mspec/runner/filters/match' + +# ActionFilter is a base class for actions that are triggered by +# specs that match the filter. The filter may be specified by +# strings that match spec descriptions or by tags for strings +# that match spec descriptions. +# +# Unlike TagFilter and RegexpFilter, ActionFilter instances do +# not affect the specs that are run. The filter is only used to +# trigger the action. + +class ActionFilter + def initialize(tags=nil, descs=nil) + @tags = Array(tags) + descs = Array(descs) + @sfilter = descs.empty? ? nil : MatchFilter.new(nil, *descs) + @tfilter = nil + end + + def ===(string) + @sfilter === string or @tfilter === string + end + + def load + return if @tags.empty? + + desc = MSpec.read_tags(@tags).map { |t| t.description } + return if desc.empty? + + @tfilter = MatchFilter.new(nil, *desc) + end + + def register + MSpec.register :load, self + end + + def unregister + MSpec.unregister :load, self + end +end diff --git a/spec/mspec/lib/mspec/runner/actions/leakchecker.rb b/spec/mspec/lib/mspec/runner/actions/leakchecker.rb new file mode 100644 index 0000000000..e947cda9ff --- /dev/null +++ b/spec/mspec/lib/mspec/runner/actions/leakchecker.rb @@ -0,0 +1,301 @@ +# Adapted from ruby's test/lib/leakchecker.rb. +# Ruby's 2-clause BSDL follows. + +# Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved. + +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. + +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. + +class LeakChecker + def initialize + @fd_info = find_fds + @tempfile_info = find_tempfiles + @thread_info = find_threads + @env_info = find_env + @argv_info = find_argv + @encoding_info = find_encodings + end + + def check(test_name) + @no_leaks = true + leaks = [ + check_fd_leak(test_name), + check_tempfile_leak(test_name), + check_thread_leak(test_name), + check_process_leak(test_name), + check_env(test_name), + check_argv(test_name), + check_encodings(test_name) + ] + GC.start if leaks.any? + return leaks.none? + end + + private + def find_fds + fd_dir = "/proc/self/fd" + if File.directory?(fd_dir) + fds = Dir.open(fd_dir) {|d| + a = d.grep(/\A\d+\z/, &:to_i) + if d.respond_to? :fileno + a -= [d.fileno] + end + a + } + fds.sort + else + [] + end + end + + def check_fd_leak(test_name) + leaked = false + live1 = @fd_info + if IO.respond_to?(:console) and (m = IO.method(:console)).arity.nonzero? + m[:close] + end + live2 = find_fds + fd_closed = live1 - live2 + if !fd_closed.empty? + fd_closed.each {|fd| + puts "Closed file descriptor: #{test_name}: #{fd}" + } + end + fd_leaked = live2 - live1 + if !fd_leaked.empty? + leaked = true + h = {} + ObjectSpace.each_object(IO) {|io| + inspect = io.inspect + begin + autoclose = io.autoclose? + fd = io.fileno + rescue IOError # closed IO object + next + end + (h[fd] ||= []) << [io, autoclose, inspect] + } + fd_leaked.each {|fd| + str = '' + if h[fd] + str << ' :' + h[fd].map {|io, autoclose, inspect| + s = ' ' + inspect + s << "(not-autoclose)" if !autoclose + s + }.sort.each {|s| + str << s + } + end + puts "Leaked file descriptor: #{test_name}: #{fd}#{str}" + } + #system("lsof -p #$$") if !fd_leaked.empty? + h.each {|fd, list| + next if list.length <= 1 + if 1 < list.count {|io, autoclose, inspect| autoclose } + str = list.map {|io, autoclose, inspect| " #{inspect}" + (autoclose ? "(autoclose)" : "") }.sort.join + puts "Multiple autoclose IO object for a file descriptor:#{str}" + end + } + end + @fd_info = live2 + return leaked + end + + def extend_tempfile_counter + return if defined? LeakChecker::TempfileCounter + m = Module.new { + @count = 0 + class << self + attr_accessor :count + end + + def new(data) + LeakChecker::TempfileCounter.count += 1 + super(data) + end + } + LeakChecker.const_set(:TempfileCounter, m) + + class << Tempfile::Remover + prepend LeakChecker::TempfileCounter + end + end + + def find_tempfiles(prev_count=-1) + return [prev_count, []] unless defined? Tempfile + extend_tempfile_counter + count = TempfileCounter.count + if prev_count == count + [prev_count, []] + else + tempfiles = ObjectSpace.each_object(Tempfile).find_all {|t| t.path } + [count, tempfiles] + end + end + + def check_tempfile_leak(test_name) + return false unless defined? Tempfile + count1, initial_tempfiles = @tempfile_info + count2, current_tempfiles = find_tempfiles(count1) + leaked = false + tempfiles_leaked = current_tempfiles - initial_tempfiles + if !tempfiles_leaked.empty? + leaked = true + list = tempfiles_leaked.map {|t| t.inspect }.sort + list.each {|str| + puts "Leaked tempfile: #{test_name}: #{str}" + } + tempfiles_leaked.each {|t| t.close! } + end + @tempfile_info = [count2, initial_tempfiles] + return leaked + end + + def find_threads + Thread.list.find_all {|t| + t != Thread.current && t.alive? + } + end + + def check_thread_leak(test_name) + live1 = @thread_info + live2 = find_threads + thread_finished = live1 - live2 + leaked = false + if !thread_finished.empty? + list = thread_finished.map {|t| t.inspect }.sort + list.each {|str| + puts "Finished thread: #{test_name}: #{str}" + } + end + thread_leaked = live2 - live1 + if !thread_leaked.empty? + leaked = true + list = thread_leaked.map {|t| t.inspect }.sort + list.each {|str| + puts "Leaked thread: #{test_name}: #{str}" + } + end + @thread_info = live2 + return leaked + end + + def check_process_leak(test_name) + subprocesses_leaked = Process.waitall + subprocesses_leaked.each { |pid, status| + puts "Leaked subprocess: #{pid}: #{status}" + } + return !subprocesses_leaked.empty? + end + + def find_env + ENV.to_h + end + + def check_env(test_name) + old_env = @env_info + new_env = find_env + return false if old_env == new_env + (old_env.keys | new_env.keys).sort.each {|k| + if old_env.has_key?(k) + if new_env.has_key?(k) + if old_env[k] != new_env[k] + puts "Environment variable changed: #{test_name} : #{k.inspect} changed : #{old_env[k].inspect} -> #{new_env[k].inspect}" + end + else + puts "Environment variable changed: #{test_name} : #{k.inspect} deleted" + end + else + if new_env.has_key?(k) + puts "Environment variable changed: #{test_name} : #{k.inspect} added" + else + flunk "unreachable" + end + end + } + @env_info = new_env + return true + end + + def find_argv + ARGV.map { |e| e.dup } + end + + def check_argv(test_name) + old_argv = @argv_info + new_argv = find_argv + leaked = false + if new_argv != old_argv + puts "ARGV changed: #{test_name} : #{old_argv.inspect} to #{new_argv.inspect}" + @argv_info = new_argv + leaked = true + end + return leaked + end + + def find_encodings + [Encoding.default_internal, Encoding.default_external] + end + + def check_encodings(test_name) + old_internal, old_external = @encoding_info + new_internal, new_external = find_encodings + leaked = false + if new_internal != old_internal + leaked = true + puts "Encoding.default_internal changed: #{test_name} : #{old_internal} to #{new_internal}" + end + if new_external != old_external + leaked = true + puts "Encoding.default_external changed: #{test_name} : #{old_external} to #{new_external}" + end + @encoding_info = [new_internal, new_external] + return leaked + end + + def puts(*args) + if @no_leaks + @no_leaks = false + print "\n" + end + super(*args) + end +end + +class LeakCheckerAction + def register + MSpec.register :start, self + MSpec.register :after, self + end + + def start + @checker = LeakChecker.new + end + + def after(state) + unless @checker.check(state.description) + if state.example + puts state.example.source_location.join(':') + end + end + end +end diff --git a/spec/mspec/lib/mspec/runner/actions/tag.rb b/spec/mspec/lib/mspec/runner/actions/tag.rb new file mode 100644 index 0000000000..760152b2a3 --- /dev/null +++ b/spec/mspec/lib/mspec/runner/actions/tag.rb @@ -0,0 +1,133 @@ +require 'mspec/runner/actions/filter' + +# TagAction - Write tagged spec description string to a +# tag file associated with each spec file. +# +# The action is triggered by specs whose descriptions +# match the filter created with 'tags' and/or 'desc' +# +# The action fires in the :after event, after the spec +# had been run. The action fires if the outcome of +# running the spec matches 'outcome'. +# +# The arguments are: +# +# action: :add, :del +# outcome: :pass, :fail, :all +# tag: the tag to create/delete +# comment: the comment to create +# tags: zero or more tags to get matching +# spec description strings from +# desc: zero or more strings to match the +# spec description strings + +class TagAction < ActionFilter + def initialize(action, outcome, tag, comment, tags=nil, descs=nil) + super tags, descs + @action = action + @outcome = outcome + @tag = tag + @comment = comment + @report = [] + @exception = false + end + + # Returns true if there are no _tag_ or _description_ filters. This + # means that a TagAction matches any example by default. Otherwise, + # returns true if either the _tag_ or the _description_ filter + # matches +string+. + def ===(string) + return true unless @sfilter or @tfilter + @sfilter === string or @tfilter === string + end + + # Callback for the MSpec :before event. Resets the +#exception?+ + # flag to false. + def before(state) + @exception = false + end + + # Callback for the MSpec :exception event. Sets the +#exception?+ + # flag to true. + def exception(exception) + @exception = true + end + + # Callback for the MSpec :after event. Performs the tag action + # depending on the type of action and the outcome of evaluating + # the example. See +TagAction+ for a description of the actions. + def after(state) + if self === state.description and outcome? + tag = SpecTag.new + tag.tag = @tag + tag.comment = @comment + tag.description = state.description + + case @action + when :add + changed = MSpec.write_tag tag + when :del + changed = MSpec.delete_tag tag + end + + @report << state.description if changed + end + end + + # Returns true if the result of evaluating the example matches + # the _outcome_ registered for this tag action. See +TagAction+ + # for a description of the _outcome_ types. + def outcome? + @outcome == :all or + (@outcome == :pass and not exception?) or + (@outcome == :fail and exception?) + end + + # Returns true if an exception was raised while evaluating the + # current example. + def exception? + @exception + end + + def report + @report.join("\n") + "\n" + end + private :report + + # Callback for the MSpec :finish event. Prints the actions + # performed while evaluating the examples. + def finish + case @action + when :add + if @report.empty? + print "\nTagAction: no specs were tagged with '#{@tag}'\n" + else + print "\nTagAction: specs tagged with '#{@tag}':\n\n" + print report + end + when :del + if @report.empty? + print "\nTagAction: no tags '#{@tag}' were deleted\n" + else + print "\nTagAction: tag '#{@tag}' deleted for specs:\n\n" + print report + end + end + end + + def register + super + MSpec.register :before, self + MSpec.register :exception, self + MSpec.register :after, self + MSpec.register :finish, self + end + + def unregister + super + MSpec.unregister :before, self + MSpec.unregister :exception, self + MSpec.unregister :after, self + MSpec.unregister :finish, self + end +end diff --git a/spec/mspec/lib/mspec/runner/actions/taglist.rb b/spec/mspec/lib/mspec/runner/actions/taglist.rb new file mode 100644 index 0000000000..c1aba53794 --- /dev/null +++ b/spec/mspec/lib/mspec/runner/actions/taglist.rb @@ -0,0 +1,56 @@ +require 'mspec/runner/actions/filter' + +# TagListAction - prints out the descriptions for any specs +# tagged with +tags+. If +tags+ is an empty list, prints out +# descriptions for any specs that are tagged. +class TagListAction + def initialize(tags=nil) + @tags = tags.nil? || tags.empty? ? nil : Array(tags) + @filter = nil + end + + # Returns true. This enables us to match any tag when loading + # tags from the file. + def include?(arg) + true + end + + # Returns true if any tagged descriptions matches +string+. + def ===(string) + @filter === string + end + + # Prints a banner about matching tagged specs. + def start + if @tags + print "\nListing specs tagged with #{@tags.map { |t| "'#{t}'" }.join(", ") }\n\n" + else + print "\nListing all tagged specs\n\n" + end + end + + # Creates a MatchFilter for specific tags or for all tags. + def load + @filter = nil + desc = MSpec.read_tags(@tags || self).map { |t| t.description } + @filter = MatchFilter.new(nil, *desc) unless desc.empty? + end + + # Prints the spec description if it matches the filter. + def after(state) + return unless self === state.description + print state.description, "\n" + end + + def register + MSpec.register :start, self + MSpec.register :load, self + MSpec.register :after, self + end + + def unregister + MSpec.unregister :start, self + MSpec.unregister :load, self + MSpec.unregister :after, self + end +end diff --git a/spec/mspec/lib/mspec/runner/actions/tagpurge.rb b/spec/mspec/lib/mspec/runner/actions/tagpurge.rb new file mode 100644 index 0000000000..f4587de6bc --- /dev/null +++ b/spec/mspec/lib/mspec/runner/actions/tagpurge.rb @@ -0,0 +1,56 @@ +require 'mspec/runner/actions/filter' +require 'mspec/runner/actions/taglist' + +# TagPurgeAction - removes all tags not matching any spec +# descriptions. +class TagPurgeAction < TagListAction + attr_reader :matching + + def initialize + @matching = [] + @filter = nil + @tags = nil + end + + # Prints a banner about purging tags. + def start + print "\nRemoving tags not matching any specs\n\n" + end + + # Creates a MatchFilter for all tags. + def load + @filter = nil + @tags = MSpec.read_tags self + desc = @tags.map { |t| t.description } + @filter = MatchFilter.new(nil, *desc) unless desc.empty? + end + + # Saves any matching tags + def after(state) + @matching << state.description if self === state.description + end + + # Rewrites any matching tags. Prints non-matching tags. + # Deletes the tag file if there were no tags (this cleans + # up empty or malformed tag files). + def unload + if @filter + matched = @tags.select { |t| @matching.any? { |s| s == t.description } } + MSpec.write_tags matched + + (@tags - matched).each { |t| print t.description, "\n" } + else + MSpec.delete_tags + end + end + + def register + super + MSpec.register :unload, self + end + + def unregister + super + MSpec.unregister :unload, self + end +end diff --git a/spec/mspec/lib/mspec/runner/actions/tally.rb b/spec/mspec/lib/mspec/runner/actions/tally.rb new file mode 100644 index 0000000000..33f937293c --- /dev/null +++ b/spec/mspec/lib/mspec/runner/actions/tally.rb @@ -0,0 +1,133 @@ +class Tally + attr_accessor :files, :examples, :expectations, :failures, :errors, :guards, :tagged + + def initialize + @files = @examples = @expectations = @failures = @errors = @guards = @tagged = 0 + end + + def files!(add=1) + @files += add + end + + def examples!(add=1) + @examples += add + end + + def expectations!(add=1) + @expectations += add + end + + def failures!(add=1) + @failures += add + end + + def errors!(add=1) + @errors += add + end + + def guards!(add=1) + @guards += add + end + + def tagged!(add=1) + @tagged += add + end + + def file + pluralize files, "file" + end + + def example + pluralize examples, "example" + end + + def expectation + pluralize expectations, "expectation" + end + + def failure + pluralize failures, "failure" + end + + def error + pluralize errors, "error" + end + + def guard + pluralize guards, "guard" + end + + def tag + "#{tagged} tagged" + end + + def format + results = [ file, example, expectation, failure, error, tag ] + if [:report, :report_on, :verify].any? { |m| MSpec.mode? m } + results << guard + end + results.join(", ") + end + + alias_method :to_s, :format + + def pluralize(count, singular) + "#{count} #{singular}#{'s' unless count == 1}" + end + private :pluralize +end + +class TallyAction + attr_reader :counter + + def initialize + @counter = Tally.new + end + + def register + MSpec.register :load, self + MSpec.register :exception, self + MSpec.register :example, self + MSpec.register :tagged, self + MSpec.register :expectation, self + end + + def unregister + MSpec.unregister :load, self + MSpec.unregister :exception, self + MSpec.unregister :example, self + MSpec.unregister :tagged, self + MSpec.unregister :expectation, self + end + + def load + @counter.files! + end + + # Callback for the MSpec :expectation event. Increments the + # tally of expectations (e.g. #should, #should_receive, etc.). + def expectation(state) + @counter.expectations! + end + + # Callback for the MSpec :exception event. Increments the + # tally of errors and failures. + def exception(exception) + exception.failure? ? @counter.failures! : @counter.errors! + end + + # Callback for the MSpec :example event. Increments the tally + # of examples. + def example(state, block) + @counter.examples! + end + + def tagged(state) + @counter.examples! + @counter.tagged! + end + + def format + @counter.format + end +end diff --git a/spec/mspec/lib/mspec/runner/actions/timer.rb b/spec/mspec/lib/mspec/runner/actions/timer.rb new file mode 100644 index 0000000000..e7ebfebe0d --- /dev/null +++ b/spec/mspec/lib/mspec/runner/actions/timer.rb @@ -0,0 +1,22 @@ +class TimerAction + def register + MSpec.register :start, self + MSpec.register :finish, self + end + + def start + @start = Time.now + end + + def finish + @stop = Time.now + end + + def elapsed + @stop - @start + end + + def format + "Finished in %f seconds" % elapsed + end +end diff --git a/spec/mspec/lib/mspec/runner/context.rb b/spec/mspec/lib/mspec/runner/context.rb new file mode 100644 index 0000000000..2b470f226a --- /dev/null +++ b/spec/mspec/lib/mspec/runner/context.rb @@ -0,0 +1,239 @@ +# Holds the state of the +describe+ block that is being +# evaluated. Every example (i.e. +it+ block) is evaluated +# in a context, which may include state set up in before +# :each or before :all blocks. +# +#-- +# A note on naming: this is named _ContextState_ rather +# than _DescribeState_ because +describe+ is the keyword +# in the DSL for refering to the context in which an example +# is evaluated, just as +it+ refers to the example itself. +#++ +class ContextState + attr_reader :state, :parent, :parents, :children, :examples, :to_s + + def initialize(mod, options=nil) + @to_s = mod.to_s + if options.is_a? Hash + @options = options + else + @to_s += "#{".:#".include?(options[0,1]) ? "" : " "}#{options}" if options + @options = { } + end + @options[:shared] ||= false + + @parsed = false + @before = { :all => [], :each => [] } + @after = { :all => [], :each => [] } + @pre = {} + @post = {} + @examples = [] + @parent = nil + @parents = [self] + @children = [] + + @mock_verify = Proc.new { Mock.verify_count } + @mock_cleanup = Proc.new { Mock.cleanup } + @expectation_missing = Proc.new { raise SpecExpectationNotFoundError } + end + + # Remove caching when a ContextState is dup'd for shared specs. + def initialize_copy(other) + @pre = {} + @post = {} + end + + # Returns true if this is a shared +ContextState+. Essentially, when + # created with: describe "Something", :shared => true { ... } + def shared? + return @options[:shared] + end + + # Set the parent (enclosing) +ContextState+ for this state. Creates + # the +parents+ list. + def parent=(parent) + @description = nil + + if shared? + @parent = nil + else + @parent = parent + parent.child self if parent + + @parents = [self] + state = parent + while state + @parents.unshift state + state = state.parent + end + end + end + + # Add the ContextState instance +child+ to the list of nested + # describe blocks. + def child(child) + @children << child + end + + # Adds a nested ContextState in a shared ContextState to a containing + # ContextState. + # + # Normal adoption is from the parent's perspective. But adopt is a good + # verb and it's reasonable for the child to adopt the parent as well. In + # this case, manipulating state from inside the child avoids needlessly + # exposing the state to manipulate it externally in the dup. (See + # #it_should_behave_like) + def adopt(parent) + self.parent = parent + + @examples = @examples.map do |example| + example = example.dup + example.context = self + example + end + + children = @children + @children = [] + + children.each { |child| child.dup.adopt self } + end + + # Returns a list of all before(+what+) blocks from self and any parents. + def pre(what) + @pre[what] ||= parents.inject([]) { |l, s| l.push(*s.before(what)) } + end + + # Returns a list of all after(+what+) blocks from self and any parents. + # The list is in reverse order. In other words, the blocks defined in + # inner describes are in the list before those defined in outer describes, + # and in a particular describe block those defined later are in the list + # before those defined earlier. + def post(what) + @post[what] ||= parents.inject([]) { |l, s| l.unshift(*s.after(what)) } + end + + # Records before(:each) and before(:all) blocks. + def before(what, &block) + return if MSpec.guarded? + block ? @before[what].push(block) : @before[what] + end + + # Records after(:each) and after(:all) blocks. + def after(what, &block) + return if MSpec.guarded? + block ? @after[what].unshift(block) : @after[what] + end + + # Creates an ExampleState instance for the block and stores it + # in a list of examples to evaluate unless the example is filtered. + def it(desc, &block) + example = ExampleState.new(self, desc, block) + MSpec.actions :add, example + return if MSpec.guarded? + @examples << example + end + + # Evaluates the block and resets the toplevel +ContextState+ to #parent. + def describe(&block) + @parsed = protect @to_s, block, false + MSpec.register_current parent + MSpec.register_shared self if shared? + end + + # Returns a description string generated from self and all parents + def description + @description ||= parents.map { |p| p.to_s }.compact.join(" ") + end + + # Injects the before/after blocks and examples from the shared + # describe block into this +ContextState+ instance. + def it_should_behave_like(desc) + return if MSpec.guarded? + + unless state = MSpec.retrieve_shared(desc) + raise Exception, "Unable to find shared 'describe' for #{desc}" + end + + state.before(:all).each { |b| before :all, &b } + state.before(:each).each { |b| before :each, &b } + state.after(:each).each { |b| after :each, &b } + state.after(:all).each { |b| after :all, &b } + + state.examples.each do |example| + example = example.dup + example.context = self + @examples << example + end + + state.children.each do |child| + child.dup.adopt self + end + end + + # Evaluates each block in +blocks+ using the +MSpec.protect+ method + # so that exceptions are handled and tallied. Returns true and does + # NOT evaluate any blocks if +check+ is true and + # MSpec.mode?(:pretend) is true. + def protect(what, blocks, check=true) + return true if check and MSpec.mode? :pretend + Array(blocks).all? { |block| MSpec.protect what, &block } + end + + # Removes filtered examples. Returns true if there are examples + # left to evaluate. + def filter_examples + filtered, @examples = @examples.partition do |ex| + ex.filtered? + end + + filtered.each do |ex| + MSpec.actions :tagged, ex + end + + !@examples.empty? + end + + # Evaluates the examples in a +ContextState+. Invokes the MSpec events + # for :enter, :before, :after, :leave. + def process + MSpec.register_current self + + if @parsed and filter_examples + MSpec.shuffle @examples if MSpec.randomize? + MSpec.actions :enter, description + + if protect "before :all", pre(:all) + @examples.each do |state| + MSpec.repeat do + @state = state + example = state.example + MSpec.actions :before, state + + if protect "before :each", pre(:each) + MSpec.clear_expectations + if example + passed = protect nil, example + MSpec.actions :example, state, example + protect nil, @expectation_missing unless MSpec.expectation? or !passed + end + end + protect "after :each", post(:each) + protect "Mock.verify_count", @mock_verify + + protect "Mock.cleanup", @mock_cleanup + MSpec.actions :after, state + @state = nil + end + end + protect "after :all", post(:all) + else + protect "Mock.cleanup", @mock_cleanup + end + + MSpec.actions :leave + end + + MSpec.register_current nil + children.each { |child| child.process } + end +end diff --git a/spec/mspec/lib/mspec/runner/evaluate.rb b/spec/mspec/lib/mspec/runner/evaluate.rb new file mode 100644 index 0000000000..fded84421f --- /dev/null +++ b/spec/mspec/lib/mspec/runner/evaluate.rb @@ -0,0 +1,54 @@ +class SpecEvaluate + def self.desc=(desc) + @desc = desc + end + + def self.desc + @desc ||= "evaluates " + end + + def initialize(ruby, desc) + @ruby = ruby.rstrip + @desc = desc || self.class.desc + end + + # Formats the Ruby source code for reabable output in the -fs formatter + # option. If the source contains no newline characters, wraps the source in + # single quotes to set if off from the rest of the description string. If + # the source does contain newline characters, sets the indent level to four + # characters. + def format(ruby, newline=true) + if ruby.include?("\n") + lines = ruby.each_line.to_a + if /( *)/ =~ lines.first + if $1.size > 4 + dedent = $1.size - 4 + ruby = lines.map { |l| l[dedent..-1] }.join + else + indent = " " * (4 - $1.size) + ruby = lines.map { |l| "#{indent}#{l}" }.join + end + end + "\n#{ruby}" + else + "'#{ruby.lstrip}'" + end + end + + def define(&block) + ruby = @ruby + desc = @desc + evaluator = self + + specify "#{desc} #{format ruby}" do + evaluator.instance_eval(ruby) + evaluator.instance_eval(&block) + end + end +end + +class Object + def evaluate(str, desc=nil, &block) + SpecEvaluate.new(str, desc).define(&block) + end +end diff --git a/spec/mspec/lib/mspec/runner/example.rb b/spec/mspec/lib/mspec/runner/example.rb new file mode 100644 index 0000000000..19eb29b079 --- /dev/null +++ b/spec/mspec/lib/mspec/runner/example.rb @@ -0,0 +1,34 @@ +require 'mspec/runner/mspec' + +# Holds some of the state of the example (i.e. +it+ block) that is +# being evaluated. See also +ContextState+. +class ExampleState + attr_reader :context, :it, :example + + def initialize(context, it, example=nil) + @context = context + @it = it + @example = example + end + + def context=(context) + @description = nil + @context = context + end + + def describe + @context.description + end + + def description + @description ||= "#{describe} #{@it}" + end + + def filtered? + incl = MSpec.retrieve(:include) || [] + excl = MSpec.retrieve(:exclude) || [] + included = incl.empty? || incl.any? { |f| f === description } + included &&= excl.empty? || !excl.any? { |f| f === description } + !included + end +end diff --git a/spec/mspec/lib/mspec/runner/exception.rb b/spec/mspec/lib/mspec/runner/exception.rb new file mode 100644 index 0000000000..0d9bb43105 --- /dev/null +++ b/spec/mspec/lib/mspec/runner/exception.rb @@ -0,0 +1,43 @@ +# Initialize $MSPEC_DEBUG +$MSPEC_DEBUG ||= false + +class ExceptionState + attr_reader :description, :describe, :it, :exception + + def initialize(state, location, exception) + @exception = exception + + @description = location ? "An exception occurred during: #{location}" : "" + if state + @description += "\n" unless @description.empty? + @description += state.description + @describe = state.describe + @it = state.it + else + @describe = @it = "" + end + end + + def failure? + [SpecExpectationNotMetError, SpecExpectationNotFoundError].any? { |e| @exception.is_a? e } + end + + def message + if @exception.message.empty? + "" + elsif @exception.class == SpecExpectationNotMetError || + @exception.class == SpecExpectationNotFoundError + @exception.message + else + "#{@exception.class}: #{@exception.message}" + end + end + + def backtrace + @backtrace_filter ||= MSpecScript.config[:backtrace_filter] + + bt = @exception.backtrace || [] + + bt.select { |line| $MSPEC_DEBUG or @backtrace_filter !~ line }.join("\n") + end +end diff --git a/spec/mspec/lib/mspec/runner/filters.rb b/spec/mspec/lib/mspec/runner/filters.rb new file mode 100644 index 0000000000..d0420faca6 --- /dev/null +++ b/spec/mspec/lib/mspec/runner/filters.rb @@ -0,0 +1,4 @@ +require 'mspec/runner/filters/match' +require 'mspec/runner/filters/regexp' +require 'mspec/runner/filters/tag' +require 'mspec/runner/filters/profile' diff --git a/spec/mspec/lib/mspec/runner/filters/match.rb b/spec/mspec/lib/mspec/runner/filters/match.rb new file mode 100644 index 0000000000..539fd02d01 --- /dev/null +++ b/spec/mspec/lib/mspec/runner/filters/match.rb @@ -0,0 +1,18 @@ +class MatchFilter + def initialize(what, *strings) + @what = what + @strings = strings + end + + def ===(string) + @strings.any? { |s| string.include?(s) } + end + + def register + MSpec.register @what, self + end + + def unregister + MSpec.unregister @what, self + end +end diff --git a/spec/mspec/lib/mspec/runner/filters/profile.rb b/spec/mspec/lib/mspec/runner/filters/profile.rb new file mode 100644 index 0000000000..a59722c451 --- /dev/null +++ b/spec/mspec/lib/mspec/runner/filters/profile.rb @@ -0,0 +1,54 @@ +class ProfileFilter + def initialize(what, *files) + @what = what + @methods = load(*files) + @pattern = /([^ .#]+[.#])([^ ]+)/ + end + + def find(name) + return name if File.exist?(File.expand_path(name)) + + ["spec/profiles", "spec", "profiles", "."].each do |dir| + file = File.join dir, name + return file if File.exist? file + end + end + + def parse(file) + pattern = /(\S+):\s*/ + key = "" + file.inject(Hash.new { |h,k| h[k] = [] }) do |hash, line| + line.chomp! + if line[0,2] == "- " + hash[key] << line[2..-1].gsub(/[ '"]/, "") + elsif m = pattern.match(line) + key = m[1] + end + hash + end + end + + def load(*files) + files.inject({}) do |hash, file| + next hash unless name = find(file) + + File.open name, "r" do |f| + hash.merge parse(f) + end + end + end + + def ===(string) + return false unless m = @pattern.match(string) + return false unless l = @methods[m[1]] + l.include? m[2] + end + + def register + MSpec.register @what, self + end + + def unregister + MSpec.unregister @what, self + end +end diff --git a/spec/mspec/lib/mspec/runner/filters/regexp.rb b/spec/mspec/lib/mspec/runner/filters/regexp.rb new file mode 100644 index 0000000000..2bd1448d3f --- /dev/null +++ b/spec/mspec/lib/mspec/runner/filters/regexp.rb @@ -0,0 +1,7 @@ +require 'mspec/runner/filters/match' + +class RegexpFilter < MatchFilter + def to_regexp(*strings) + strings.map { |str| Regexp.new str } + end +end diff --git a/spec/mspec/lib/mspec/runner/filters/tag.rb b/spec/mspec/lib/mspec/runner/filters/tag.rb new file mode 100644 index 0000000000..c641c01606 --- /dev/null +++ b/spec/mspec/lib/mspec/runner/filters/tag.rb @@ -0,0 +1,29 @@ +class TagFilter + def initialize(what, *tags) + @what = what + @tags = tags + end + + def load + @descriptions = MSpec.read_tags(@tags).map { |t| t.description } + MSpec.register @what, self + end + + def unload + MSpec.unregister @what, self + end + + def ===(string) + @descriptions.include?(string) + end + + def register + MSpec.register :load, self + MSpec.register :unload, self + end + + def unregister + MSpec.unregister :load, self + MSpec.unregister :unload, self + end +end diff --git a/spec/mspec/lib/mspec/runner/formatters.rb b/spec/mspec/lib/mspec/runner/formatters.rb new file mode 100644 index 0000000000..d085031a12 --- /dev/null +++ b/spec/mspec/lib/mspec/runner/formatters.rb @@ -0,0 +1,12 @@ +require 'mspec/runner/formatters/describe' +require 'mspec/runner/formatters/dotted' +require 'mspec/runner/formatters/file' +require 'mspec/runner/formatters/specdoc' +require 'mspec/runner/formatters/html' +require 'mspec/runner/formatters/summary' +require 'mspec/runner/formatters/unit' +require 'mspec/runner/formatters/spinner' +require 'mspec/runner/formatters/method' +require 'mspec/runner/formatters/yaml' +require 'mspec/runner/formatters/profile' +require 'mspec/runner/formatters/junit' diff --git a/spec/mspec/lib/mspec/runner/formatters/describe.rb b/spec/mspec/lib/mspec/runner/formatters/describe.rb new file mode 100644 index 0000000000..176bd79279 --- /dev/null +++ b/spec/mspec/lib/mspec/runner/formatters/describe.rb @@ -0,0 +1,24 @@ +require 'mspec/runner/formatters/dotted' +require 'mspec/runner/actions/tally' + +class DescribeFormatter < DottedFormatter + # Callback for the MSpec :finish event. Prints a summary of + # the number of errors and failures for each +describe+ block. + def finish + describes = Hash.new { |h,k| h[k] = Tally.new } + + @exceptions.each do |exc| + desc = describes[exc.describe] + exc.failure? ? desc.failures! : desc.errors! + end + + print "\n" + describes.each do |d, t| + text = d.size > 40 ? "#{d[0,37]}..." : d.ljust(40) + print "\n#{text} #{t.failure}, #{t.error}" + end + print "\n" unless describes.empty? + + print "\n#{@timer.format}\n\n#{@tally.format}\n" + end +end diff --git a/spec/mspec/lib/mspec/runner/formatters/dotted.rb b/spec/mspec/lib/mspec/runner/formatters/dotted.rb new file mode 100644 index 0000000000..61c8e4c27c --- /dev/null +++ b/spec/mspec/lib/mspec/runner/formatters/dotted.rb @@ -0,0 +1,117 @@ +require 'mspec/expectations/expectations' +require 'mspec/runner/actions/timer' +require 'mspec/runner/actions/tally' +require 'mspec/runner/actions/leakchecker' if ENV['CHECK_LEAKS'] + +class DottedFormatter + attr_reader :exceptions, :timer, :tally + + def initialize(out=nil) + @exception = @failure = false + @exceptions = [] + @count = 0 # For subclasses + if out.nil? + @out = $stdout + else + @out = File.open out, "w" + end + + @current_state = nil + end + + # Creates the +TimerAction+ and +TallyAction+ instances and + # registers them. Registers +self+ for the +:exception+, + # +:before+, +:after+, and +:finish+ actions. + def register + (@timer = TimerAction.new).register + (@tally = TallyAction.new).register + LeakCheckerAction.new.register if ENV['CHECK_LEAKS'] + @counter = @tally.counter + + MSpec.register :exception, self + MSpec.register :before, self + MSpec.register :after, self + MSpec.register :finish, self + MSpec.register :abort, self + end + + def abort + if @current_state + puts "\naborting example: #{@current_state.description}" + end + end + + # Returns true if any exception is raised while running + # an example. This flag is reset before each example + # is evaluated. + def exception? + @exception + end + + # Returns true if all exceptions during the evaluation + # of an example are failures rather than errors. See + # ExceptionState#failure. This flag is reset + # before each example is evaluated. + def failure? + @failure + end + + # Callback for the MSpec :before event. Resets the + # +#exception?+ and +#failure+ flags. + def before(state=nil) + @current_state = state + @failure = @exception = false + end + + # Callback for the MSpec :exception event. Stores the + # +ExceptionState+ object to generate the list of backtraces + # after all the specs are run. Also updates the internal + # +#exception?+ and +#failure?+ flags. + def exception(exception) + @count += 1 + @failure = @exception ? @failure && exception.failure? : exception.failure? + @exception = true + @exceptions << exception + end + + # Callback for the MSpec :after event. Prints an indicator + # for the result of evaluating this example as follows: + # . = No failure or error + # F = An SpecExpectationNotMetError was raised + # E = Any exception other than SpecExpectationNotMetError + def after(state = nil) + @current_state = nil + + unless exception? + print "." + else + print failure? ? "F" : "E" + end + end + + # Callback for the MSpec :finish event. Prints a description + # and backtrace for every exception that occurred while + # evaluating the examples. + def finish + print "\n" + count = 0 + @exceptions.each do |exc| + count += 1 + print_exception(exc, count) + end + print "\n#{@timer.format}\n\n#{@tally.format}\n" + end + + def print_exception(exc, count) + outcome = exc.failure? ? "FAILED" : "ERROR" + print "\n#{count})\n#{exc.description} #{outcome}\n" + print exc.message, "\n" + print exc.backtrace, "\n" + end + + # A convenience method to allow printing to different outputs. + def print(*args) + @out.print(*args) + @out.flush + end +end diff --git a/spec/mspec/lib/mspec/runner/formatters/file.rb b/spec/mspec/lib/mspec/runner/formatters/file.rb new file mode 100644 index 0000000000..6db72af4ff --- /dev/null +++ b/spec/mspec/lib/mspec/runner/formatters/file.rb @@ -0,0 +1,19 @@ +require 'mspec/runner/formatters/dotted' + +class FileFormatter < DottedFormatter + # Unregisters DottedFormatter#before, #after methods and + # registers #load, #unload, which perform the same duties + # as #before, #after in DottedFormatter. + def register + super + + MSpec.unregister :before, self + MSpec.unregister :after, self + + MSpec.register :load, self + MSpec.register :unload, self + end + + alias_method :load, :before + alias_method :unload, :after +end diff --git a/spec/mspec/lib/mspec/runner/formatters/html.rb b/spec/mspec/lib/mspec/runner/formatters/html.rb new file mode 100644 index 0000000000..060d2732f0 --- /dev/null +++ b/spec/mspec/lib/mspec/runner/formatters/html.rb @@ -0,0 +1,81 @@ +require 'mspec/expectations/expectations' +require 'mspec/runner/formatters/dotted' + +class HtmlFormatter < DottedFormatter + def register + super + MSpec.register :start, self + MSpec.register :enter, self + MSpec.register :leave, self + end + + def start + print <<-EOH + + + +Spec Output For #{RUBY_NAME} (#{RUBY_VERSION}) + + + +EOH + end + + def enter(describe) + print "

#{describe}

\n
    \n" + end + + def leave + print "
\n
\n" + end + + def exception(exception) + super + outcome = exception.failure? ? "FAILED" : "ERROR" + print %[
  • - #{exception.it} (] + print %[#{outcome} - #{@count})
  • \n] + end + + def after(state) + print %[
  • - #{state.it}
  • \n] unless exception? + end + + def finish + success = @exceptions.empty? + unless success + print "
    \n" + print %[
      ] + count = 0 + @exceptions.each do |exc| + outcome = exc.failure? ? "FAILED" : "ERROR" + print %[\n
    1. #{escape(exc.description)} #{outcome}

      \n

      ] + print escape(exc.message) + print "

      \n
      \n"
      +        print escape(exc.backtrace)
      +        print "
      \n
    2. \n" + end + print "
    \n" + end + print %[

    #{@timer.format}

    \n] + print %[

    #{@tally.format}

    \n] + print "\n\n" + end + + def escape(string) + string.gsub("&", " ").gsub("<", "<").gsub(">", ">") + end +end diff --git a/spec/mspec/lib/mspec/runner/formatters/junit.rb b/spec/mspec/lib/mspec/runner/formatters/junit.rb new file mode 100644 index 0000000000..647deee7e1 --- /dev/null +++ b/spec/mspec/lib/mspec/runner/formatters/junit.rb @@ -0,0 +1,89 @@ +require 'mspec/expectations/expectations' +require 'mspec/utils/ruby_name' +require 'mspec/runner/formatters/yaml' + +class JUnitFormatter < YamlFormatter + def initialize(out=nil) + super + @tests = [] + end + + def after(state = nil) + super + @tests << {:test => state, :exception => false} unless exception? + end + + def exception(exception) + super + @tests << {:test => exception, :exception => true} + end + + def finish + switch + + time = @timer.elapsed + tests = @tally.counter.examples + errors = @tally.counter.errors + failures = @tally.counter.failures + + printf <<-XML + + + + + XML + @tests.each do |h| + description = encode_for_xml h[:test].description + + printf <<-XML, "Spec", description, 0.0 + + XML + if h[:exception] + outcome = h[:test].failure? ? "failure" : "error" + message = encode_for_xml h[:test].message + backtrace = encode_for_xml h[:test].backtrace + print <<-XML + <#{outcome} message="error in #{description}" type="#{outcome}"> + #{message} + #{backtrace} + + XML + end + print <<-XML + + XML + end + + print <<-XML + + + XML + end + + private + LT = "<" + GT = ">" + QU = """ + AP = "'" + AM = "&" + TARGET_ENCODING = "ISO-8859-1" + + def encode_for_xml(str) + encode_as_latin1(str).gsub("<", LT).gsub(">", GT). + gsub('"', QU).gsub("'", AP).gsub("&", AM). + tr("\x00-\x08", "?") + end + + def encode_as_latin1(str) + str.encode(TARGET_ENCODING, :undef => :replace, :invalid => :replace) + end +end diff --git a/spec/mspec/lib/mspec/runner/formatters/method.rb b/spec/mspec/lib/mspec/runner/formatters/method.rb new file mode 100644 index 0000000000..ff115193fd --- /dev/null +++ b/spec/mspec/lib/mspec/runner/formatters/method.rb @@ -0,0 +1,93 @@ +require 'mspec/runner/formatters/dotted' + +class MethodFormatter < DottedFormatter + attr_accessor :methods + + def initialize(out=nil) + super + @methods = Hash.new do |h, k| + hash = {} + hash[:examples] = 0 + hash[:expectations] = 0 + hash[:failures] = 0 + hash[:errors] = 0 + hash[:exceptions] = [] + h[k] = hash + end + end + + # Returns the type of method as a "class", "instance", + # or "unknown". + def method_type(sep) + case sep + when '.', '::' + "class" + when '#' + "instance" + else + "unknown" + end + end + + # Callback for the MSpec :before event. Parses the + # describe string into class and method if possible. + # Resets the tallies so the counts are only for this + # example. + def before(state) + super + + # The pattern for a method name is not correctly + # restrictive but it is simplistic and useful + # for our purpose. + /^([A-Za-z_]+\w*)(\.|#|::)([^ ]+)/ =~ state.describe + @key = $1 && $2 && $3 ? "#{$1}#{$2}#{$3}" : state.describe + + unless methods.key? @key + h = methods[@key] + h[:class] = "#{$1}" + h[:method] = "#{$3}" + h[:type] = method_type $2 + h[:description] = state.description + end + + tally.counter.examples = 0 + tally.counter.expectations = 0 + tally.counter.failures = 0 + tally.counter.errors = 0 + + @exceptions = [] + end + + # Callback for the MSpec :after event. Sets or adds to + # tallies for the example block. + def after(state) + h = methods[@key] + h[:examples] += tally.counter.examples + h[:expectations] += tally.counter.expectations + h[:failures] += tally.counter.failures + h[:errors] += tally.counter.errors + @exceptions.each do |exc| + h[:exceptions] << "#{exc.message}\n#{exc.backtrace}\n" + end + end + + # Callback for the MSpec :finish event. Prints out the + # summary information in YAML format for all the methods. + def finish + print "---\n" + + methods.each do |key, hash| + print key.inspect, ":\n" + print " class: ", hash[:class].inspect, "\n" + print " method: ", hash[:method].inspect, "\n" + print " type: ", hash[:type], "\n" + print " description: ", hash[:description].inspect, "\n" + print " examples: ", hash[:examples], "\n" + print " expectations: ", hash[:expectations], "\n" + print " failures: ", hash[:failures], "\n" + print " errors: ", hash[:errors], "\n" + print " exceptions:\n" + hash[:exceptions].each { |exc| print " - ", exc.inspect, "\n" } + end + end +end diff --git a/spec/mspec/lib/mspec/runner/formatters/multi.rb b/spec/mspec/lib/mspec/runner/formatters/multi.rb new file mode 100644 index 0000000000..bcc5411e6f --- /dev/null +++ b/spec/mspec/lib/mspec/runner/formatters/multi.rb @@ -0,0 +1,36 @@ +require 'mspec/runner/formatters/spinner' +require 'yaml' + +class MultiFormatter < SpinnerFormatter + def initialize(out=nil) + super(out) + @counter = @tally = Tally.new + @timer = TimerAction.new + @timer.start + end + + def aggregate_results(files) + @timer.finish + @exceptions = [] + + files.each do |file| + d = File.open(file, "r") { |f| YAML.load f } + File.delete file + + @exceptions += Array(d['exceptions']) + @tally.files! d['files'] + @tally.examples! d['examples'] + @tally.expectations! d['expectations'] + @tally.errors! d['errors'] + @tally.failures! d['failures'] + end + end + + def print_exception(exc, count) + print "\n#{count})\n#{exc}\n" + end + + def finish + super(false) + end +end diff --git a/spec/mspec/lib/mspec/runner/formatters/profile.rb b/spec/mspec/lib/mspec/runner/formatters/profile.rb new file mode 100644 index 0000000000..498cd4a3b7 --- /dev/null +++ b/spec/mspec/lib/mspec/runner/formatters/profile.rb @@ -0,0 +1,70 @@ +require 'mspec/expectations/expectations' +require 'mspec/runner/formatters/dotted' + +class ProfileFormatter < DottedFormatter + def initialize(out=nil) + super + + @describe_name = nil + @describe_time = nil + @describes = [] + @its = [] + end + + def register + super + MSpec.register :enter, self + end + + # Callback for the MSpec :enter event. Prints the + # +describe+ block string. + def enter(describe) + if @describe_time + @describes << [@describe_name, Time.now.to_f - @describe_time] + end + + @describe_name = describe + @describe_time = Time.now.to_f + end + + # Callback for the MSpec :before event. Prints the + # +it+ block string. + def before(state) + super + + @it_name = state.it + @it_time = Time.now.to_f + end + + # Callback for the MSpec :after event. Prints a + # newline to finish the description string output. + def after(state) + @its << [@describe_name, @it_name, Time.now.to_f - @it_time] + super + end + + def finish + puts "\nProfiling info:" + + desc = @describes.sort { |a,b| b.last <=> a.last } + desc.delete_if { |a| a.last <= 0.001 } + show = desc[0, 100] + + puts "Top #{show.size} describes:" + + show.each do |des, time| + printf "%3.3f - %s\n", time, des + end + + its = @its.sort { |a,b| b.last <=> a.last } + its.delete_if { |a| a.last <= 0.001 } + show = its[0, 100] + + puts "\nTop #{show.size} its:" + show.each do |des, it, time| + printf "%3.3f - %s %s\n", time, des, it + end + + super + end +end diff --git a/spec/mspec/lib/mspec/runner/formatters/specdoc.rb b/spec/mspec/lib/mspec/runner/formatters/specdoc.rb new file mode 100644 index 0000000000..29adde3c5c --- /dev/null +++ b/spec/mspec/lib/mspec/runner/formatters/specdoc.rb @@ -0,0 +1,41 @@ +require 'mspec/expectations/expectations' +require 'mspec/runner/formatters/dotted' + +class SpecdocFormatter < DottedFormatter + def register + super + MSpec.register :enter, self + end + + # Callback for the MSpec :enter event. Prints the + # +describe+ block string. + def enter(describe) + print "\n#{describe}\n" + end + + # Callback for the MSpec :before event. Prints the + # +it+ block string. + def before(state) + super + print "- #{state.it}" + end + + # Callback for the MSpec :exception event. Prints + # either 'ERROR - X' or 'FAILED - X' where _X_ is + # the sequential number of the exception raised. If + # there has already been an exception raised while + # evaluating this example, it prints another +it+ + # block description string so that each discription + # string has an associated 'ERROR' or 'FAILED' + def exception(exception) + print "\n- #{exception.it}" if exception? + super + print " (#{exception.failure? ? 'FAILED' : 'ERROR'} - #{@count})" + end + + # Callback for the MSpec :after event. Prints a + # newline to finish the description string output. + def after(state) + print "\n" + end +end diff --git a/spec/mspec/lib/mspec/runner/formatters/spinner.rb b/spec/mspec/lib/mspec/runner/formatters/spinner.rb new file mode 100644 index 0000000000..f6f35cc476 --- /dev/null +++ b/spec/mspec/lib/mspec/runner/formatters/spinner.rb @@ -0,0 +1,117 @@ +require 'mspec/expectations/expectations' +require 'mspec/runner/formatters/dotted' + +class SpinnerFormatter < DottedFormatter + attr_reader :length + + Spins = %w!| / - \\! + HOUR = 3600 + MIN = 60 + + def initialize(out=nil) + super(nil) + + @which = 0 + @loaded = 0 + self.length = 40 + @percent = 0 + @start = Time.now + + term = ENV['TERM'] + @color = (term != "dumb") + @fail_color = "32" + @error_color = "32" + end + + def register + super + + MSpec.register :start, self + MSpec.register :unload, self + MSpec.unregister :before, self + end + + def length=(length) + @length = length + @ratio = 100.0 / length + @position = length / 2 - 2 + end + + def compute_etr + return @etr = "00:00:00" if @percent == 0 + elapsed = Time.now - @start + remain = (100 * elapsed / @percent) - elapsed + + hour = remain >= HOUR ? (remain / HOUR).to_i : 0 + remain -= hour * HOUR + min = remain >= MIN ? (remain / MIN).to_i : 0 + sec = remain - min * MIN + + @etr = "%02d:%02d:%02d" % [hour, min, sec] + end + + def compute_percentage + @percent = @loaded * 100 / @total + bar = ("=" * (@percent / @ratio)).ljust @length + label = "%d%%" % @percent + bar[@position, label.size] = label + @bar = bar + end + + def compute_progress + compute_percentage + compute_etr + end + + def progress_line + @which = (@which + 1) % Spins.size + data = [Spins[@which], @bar, @etr, @counter.failures, @counter.errors] + if @color + "\r[%s | %s | %s] \e[0;#{@fail_color}m%6dF \e[0;#{@error_color}m%6dE\e[0m " % data + else + "\r[%s | %s | %s] %6dF %6dE " % data + end + end + + def clear_progress_line + print "\r#{' '*progress_line.length}" + end + + # Callback for the MSpec :start event. Stores the total + # number of files that will be processed. + def start + @total = MSpec.retrieve(:files).size + compute_progress + print progress_line + end + + # Callback for the MSpec :unload event. Increments the number + # of files that have been run. + def unload + @loaded += 1 + compute_progress + print progress_line + end + + # Callback for the MSpec :exception event. Changes the color + # used to display the tally of errors and failures + def exception(exception) + super + @fail_color = "31" if exception.failure? + @error_color = "33" unless exception.failure? + + clear_progress_line + print_exception(exception, @count) + end + + # Callback for the MSpec :after event. Updates the spinner. + def after(state) + print progress_line + end + + def finish(printed_exceptions = true) + # We already printed the exceptions + @exceptions = [] if printed_exceptions + super() + end +end diff --git a/spec/mspec/lib/mspec/runner/formatters/summary.rb b/spec/mspec/lib/mspec/runner/formatters/summary.rb new file mode 100644 index 0000000000..0c9207194c --- /dev/null +++ b/spec/mspec/lib/mspec/runner/formatters/summary.rb @@ -0,0 +1,11 @@ +require 'mspec/expectations/expectations' +require 'mspec/runner/formatters/dotted' + +class SummaryFormatter < DottedFormatter + # Callback for the MSpec :after event. Overrides the + # callback provided by +DottedFormatter+ and does not + # print any output for each example evaluated. + def after(state) + # do nothing + end +end diff --git a/spec/mspec/lib/mspec/runner/formatters/unit.rb b/spec/mspec/lib/mspec/runner/formatters/unit.rb new file mode 100644 index 0000000000..69b68dc0d5 --- /dev/null +++ b/spec/mspec/lib/mspec/runner/formatters/unit.rb @@ -0,0 +1,21 @@ +require 'mspec/expectations/expectations' +require 'mspec/runner/formatters/dotted' + +class UnitdiffFormatter < DottedFormatter + def finish + print "\n\n#{@timer.format}\n" + count = 0 + @exceptions.each do |exc| + outcome = exc.failure? ? "FAILED" : "ERROR" + print "\n#{count += 1})\n#{exc.description} #{outcome}\n" + print exc.message, ": \n" + print exc.backtrace, "\n" + end + print "\n#{@tally.format}\n" + end + + def backtrace(exc) + exc.backtrace && exc.backtrace.join("\n") + end + private :backtrace +end diff --git a/spec/mspec/lib/mspec/runner/formatters/yaml.rb b/spec/mspec/lib/mspec/runner/formatters/yaml.rb new file mode 100644 index 0000000000..090a9b1b9d --- /dev/null +++ b/spec/mspec/lib/mspec/runner/formatters/yaml.rb @@ -0,0 +1,42 @@ +require 'mspec/expectations/expectations' +require 'mspec/runner/formatters/dotted' + +class YamlFormatter < DottedFormatter + def initialize(out=nil) + super(nil) + + if out.nil? + @finish = $stdout + else + @finish = File.open out, "w" + end + end + + def switch + @out = @finish + end + + def after(state) + end + + def finish + switch + + print "---\n" + print "exceptions:\n" + @exceptions.each do |exc| + outcome = exc.failure? ? "FAILED" : "ERROR" + str = "#{exc.description} #{outcome}\n" + str << exc.message << "\n" << exc.backtrace + print "- ", str.inspect, "\n" + end + + print "time: ", @timer.elapsed, "\n" + print "files: ", @tally.counter.files, "\n" + print "examples: ", @tally.counter.examples, "\n" + print "expectations: ", @tally.counter.expectations, "\n" + print "failures: ", @tally.counter.failures, "\n" + print "errors: ", @tally.counter.errors, "\n" + print "tagged: ", @tally.counter.tagged, "\n" + end +end diff --git a/spec/mspec/lib/mspec/runner/mspec.rb b/spec/mspec/lib/mspec/runner/mspec.rb new file mode 100644 index 0000000000..0ff0de36ca --- /dev/null +++ b/spec/mspec/lib/mspec/runner/mspec.rb @@ -0,0 +1,391 @@ +require 'mspec/runner/context' +require 'mspec/runner/exception' +require 'mspec/runner/tag' + +module MSpec + + @exit = nil + @abort = nil + @start = nil + @enter = nil + @before = nil + @add = nil + @after = nil + @leave = nil + @finish = nil + @exclude = nil + @include = nil + @leave = nil + @load = nil + @unload = nil + @tagged = nil + @current = nil + @example = nil + @modes = [] + @shared = {} + @guarded = [] + @features = {} + @exception = nil + @randomize = nil + @repeat = nil + @expectation = nil + @expectations = false + + def self.describe(mod, options=nil, &block) + state = ContextState.new mod, options + state.parent = current + + MSpec.register_current state + state.describe(&block) + + state.process unless state.shared? or current + end + + def self.process + STDOUT.puts RUBY_DESCRIPTION + + actions :start + files + actions :finish + end + + def self.each_file(&block) + if ENV["MSPEC_MULTI"] + STDOUT.print "." + STDOUT.flush + while (file = STDIN.gets.chomp) != "QUIT" + yield file + STDOUT.print "." + STDOUT.flush + end + else + return unless files = retrieve(:files) + shuffle files if randomize? + files.each(&block) + end + end + + def self.files + each_file do |file| + setup_env + store :file, file + actions :load + protect("loading #{file}") { Kernel.load file } + actions :unload + end + end + + def self.setup_env + @env = Object.new + @env.extend MSpec + end + + def self.actions(action, *args) + actions = retrieve(action) + actions.each { |obj| obj.send action, *args } if actions + end + + def self.protect(location, &block) + begin + @env.instance_eval(&block) + return true + rescue SystemExit => e + raise e + rescue Exception => exc + register_exit 1 + actions :exception, ExceptionState.new(current && current.state, location, exc) + return false + end + end + + # Guards can be nested, so a stack is necessary to know when we have + # exited the toplevel guard. + def self.guard + @guarded << true + end + + def self.unguard + @guarded.pop + end + + def self.guarded? + !@guarded.empty? + end + + # Sets the toplevel ContextState to +state+. + def self.register_current(state) + store :current, state + end + + # Sets the toplevel ContextState to +nil+. + def self.clear_current + store :current, nil + end + + # Returns the toplevel ContextState. + def self.current + retrieve :current + end + + # Stores the shared ContextState keyed by description. + def self.register_shared(state) + @shared[state.to_s] = state + end + + # Returns the shared ContextState matching description. + def self.retrieve_shared(desc) + @shared[desc.to_s] + end + + # Stores the exit code used by the runner scripts. + def self.register_exit(code) + store :exit, code + end + + # Retrieves the stored exit code. + def self.exit_code + retrieve(:exit).to_i + end + + # Stores the list of files to be evaluated. + def self.register_files(files) + store :files, files + end + + # Stores one or more substitution patterns for transforming + # a spec filename into a tags filename, where each pattern + # has the form: + # + # [Regexp, String] + # + # See also +tags_file+. + def self.register_tags_patterns(patterns) + store :tags_patterns, patterns + end + + # Registers an operating mode. Modes recognized by MSpec: + # + # :pretend - actions execute but specs are not run + # :verify - specs are run despite guards and the result is + # verified to match the expectation of the guard + # :report - specs that are guarded are reported + # :unguarded - all guards are forced off + def self.register_mode(mode) + modes = retrieve :modes + modes << mode unless modes.include? mode + end + + # Clears all registered modes. + def self.clear_modes + store :modes, [] + end + + # Returns +true+ if +mode+ is registered. + def self.mode?(mode) + retrieve(:modes).include? mode + end + + def self.enable_feature(feature) + retrieve(:features)[feature] = true + end + + def self.disable_feature(feature) + retrieve(:features)[feature] = false + end + + def self.feature_enabled?(feature) + retrieve(:features)[feature] || false + end + + def self.retrieve(symbol) + instance_variable_get :"@#{symbol}" + end + + def self.store(symbol, value) + instance_variable_set :"@#{symbol}", value + end + + # This method is used for registering actions that are + # run at particular points in the spec cycle: + # :start before any specs are run + # :load before a spec file is loaded + # :enter before a describe block is run + # :before before a single spec is run + # :add while a describe block is adding examples to run later + # :expectation before a 'should', 'should_receive', etc. + # :example after an example block is run, passed the block + # :exception after an exception is rescued + # :after after a single spec is run + # :leave after a describe block is run + # :unload after a spec file is run + # :finish after all specs are run + # + # Objects registered as actions above should respond to + # a method of the same name. For example, if an object + # is registered as a :start action, it should respond to + # a #start method call. + # + # Additionally, there are two "action" lists for + # filtering specs: + # :include return true if the spec should be run + # :exclude return true if the spec should NOT be run + # + def self.register(symbol, action) + unless value = retrieve(symbol) + value = store symbol, [] + end + value << action unless value.include? action + end + + def self.unregister(symbol, action) + if value = retrieve(symbol) + value.delete action + end + end + + def self.randomize(flag=true) + @randomize = flag + end + + def self.randomize? + @randomize == true + end + + def self.repeat=(times) + @repeat = times + end + + def self.repeat + (@repeat || 1).times do + yield + end + end + + def self.shuffle(ary) + return if ary.empty? + + size = ary.size + size.times do |i| + r = rand(size - i - 1) + ary[i], ary[r] = ary[r], ary[i] + end + end + + # Records that an expectation has been encountered in an example. + def self.expectation + store :expectations, true + end + + # Returns true if an expectation has been encountered + def self.expectation? + retrieve :expectations + end + + # Resets the flag that an expectation has been encountered in an example. + def self.clear_expectations + store :expectations, false + end + + # Transforms a spec filename into a tags filename by applying each + # substitution pattern in :tags_pattern. The default patterns are: + # + # [%r(/spec/), '/spec/tags/'], [/_spec.rb$/, '_tags.txt'] + # + # which will perform the following transformation: + # + # path/to/spec/class/method_spec.rb => path/to/spec/tags/class/method_tags.txt + # + # See also +register_tags_patterns+. + def self.tags_file + patterns = retrieve(:tags_patterns) || + [[%r(spec/), 'spec/tags/'], [/_spec.rb$/, '_tags.txt']] + patterns.inject(retrieve(:file).dup) do |file, pattern| + file.gsub(*pattern) + end + end + + # Returns a list of tags matching any tag string in +keys+ based + # on the return value of keys.include?("tag_name") + def self.read_tags(keys) + tags = [] + file = tags_file + if File.exist? file + File.open(file, "r:utf-8") do |f| + f.each_line do |line| + line.chomp! + next if line.empty? + tag = SpecTag.new line + tags << tag if keys.include? tag.tag + end + end + end + tags + end + + def self.make_tag_dir(path) + parent = File.dirname(path) + return if File.exist? parent + begin + Dir.mkdir(parent) + rescue SystemCallError + make_tag_dir(parent) + Dir.mkdir(parent) + end + end + + # Writes each tag in +tags+ to the tag file. Overwrites the + # tag file if it exists. + def self.write_tags(tags) + file = tags_file + make_tag_dir(file) + File.open(file, "w:utf-8") do |f| + tags.each { |t| f.puts t } + end + end + + # Writes +tag+ to the tag file if it does not already exist. + # Returns +true+ if the tag is written, +false+ otherwise. + def self.write_tag(tag) + tags = read_tags([tag.tag]) + tags.each do |t| + if t.tag == tag.tag and t.description == tag.description + return false + end + end + + file = tags_file + make_tag_dir(file) + File.open(file, "a:utf-8") { |f| f.puts tag.to_s } + return true + end + + # Deletes +tag+ from the tag file if it exists. Returns +true+ + # if the tag is deleted, +false+ otherwise. Deletes the tag + # file if it is empty. + def self.delete_tag(tag) + deleted = false + desc = tag.escape(tag.description) + file = tags_file + if File.exist? file + lines = IO.readlines(file) + File.open(file, "w:utf-8") do |f| + lines.each do |line| + line = line.chomp + if line.start_with?(tag.tag) and line.end_with?(desc) + deleted = true + else + f.puts line unless line.empty? + end + end + end + File.delete file unless File.size? file + end + return deleted + end + + # Removes the tag file associated with a spec file. + def self.delete_tags + file = tags_file + File.delete file if File.exist? file + end +end diff --git a/spec/mspec/lib/mspec/runner/object.rb b/spec/mspec/lib/mspec/runner/object.rb new file mode 100644 index 0000000000..018e356149 --- /dev/null +++ b/spec/mspec/lib/mspec/runner/object.rb @@ -0,0 +1,28 @@ +class Object + def before(at=:each, &block) + MSpec.current.before at, &block + end + + def after(at=:each, &block) + MSpec.current.after at, &block + end + + def describe(mod, msg=nil, options=nil, &block) + MSpec.describe mod, msg, &block + end + + def it(msg, &block) + MSpec.current.it msg, &block + end + + def it_should_behave_like(desc) + MSpec.current.it_should_behave_like desc + end + + # For ReadRuby compatiability + def doc(*a) + end + + alias_method :context, :describe + alias_method :specify, :it +end diff --git a/spec/mspec/lib/mspec/runner/shared.rb b/spec/mspec/lib/mspec/runner/shared.rb new file mode 100644 index 0000000000..336e35f6ac --- /dev/null +++ b/spec/mspec/lib/mspec/runner/shared.rb @@ -0,0 +1,12 @@ +require 'mspec/runner/mspec' + +class Object + def it_behaves_like(desc, meth, obj=nil) + send :before, :all do + @method = meth + @object = obj + end + + send :it_should_behave_like, desc.to_s + end +end diff --git a/spec/mspec/lib/mspec/runner/tag.rb b/spec/mspec/lib/mspec/runner/tag.rb new file mode 100644 index 0000000000..e2275ad3a6 --- /dev/null +++ b/spec/mspec/lib/mspec/runner/tag.rb @@ -0,0 +1,38 @@ +class SpecTag + attr_accessor :tag, :comment, :description + + def initialize(string=nil) + parse(string) if string + end + + def parse(string) + m = /^([^()#:]+)(\(([^)]+)?\))?:(.*)$/.match string + @tag, @comment, description = m.values_at(1, 3, 4) if m + @description = unescape description + end + + def unescape(str) + return unless str + if str[0] == ?" and str[-1] == ?" + str[1..-2].gsub('\n', "\n") + else + str + end + end + + def escape(str) + if str.include? "\n" + %["#{str.gsub("\n", '\n')}"] + else + str + end + end + + def to_s + "#{@tag}#{ "(#{@comment})" if @comment }:#{escape @description}" + end + + def ==(o) + @tag == o.tag and @comment == o.comment and @description == o.description + end +end diff --git a/spec/mspec/lib/mspec/utils/deprecate.rb b/spec/mspec/lib/mspec/utils/deprecate.rb new file mode 100644 index 0000000000..1db843b329 --- /dev/null +++ b/spec/mspec/lib/mspec/utils/deprecate.rb @@ -0,0 +1,6 @@ +module MSpec + def self.deprecate(what, replacement) + user_caller = caller.find { |line| !line.include?('lib/mspec') } + $stderr.puts "\n#{what} is deprecated, use #{replacement} instead.\nfrom #{user_caller}" + end +end diff --git a/spec/mspec/lib/mspec/utils/name_map.rb b/spec/mspec/lib/mspec/utils/name_map.rb new file mode 100644 index 0000000000..c1de081af0 --- /dev/null +++ b/spec/mspec/lib/mspec/utils/name_map.rb @@ -0,0 +1,128 @@ +class NameMap + MAP = { + '`' => 'backtick', + '+' => 'plus', + '-' => 'minus', + '+@' => 'uplus', + '-@' => 'uminus', + '*' => 'multiply', + '/' => 'divide', + '%' => 'modulo', + '<<' => {'Bignum' => 'left_shift', + 'Fixnum' => 'left_shift', + 'IO' => 'output', + :default => 'append' }, + '>>' => 'right_shift', + '<' => 'lt', + '<=' => 'lte', + '>' => 'gt', + '>=' => 'gte', + '=' => 'assignment', + '==' => 'equal_value', + '===' => 'case_compare', + '<=>' => 'comparison', + '[]' => 'element_reference', + '[]=' => 'element_set', + '**' => 'exponent', + '!' => 'not', + '~' => {'Bignum' => 'complement', + 'Fixnum' => 'complement', + 'Regexp' => 'match', + 'String' => 'match' }, + '!=' => 'not_equal', + '!~' => 'not_match', + '=~' => 'match', + '&' => {'Bignum' => 'bit_and', + 'Fixnum' => 'bit_and', + 'Array' => 'intersection', + 'TrueClass' => 'and', + 'FalseClass' => 'and', + 'NilClass' => 'and', + 'Set' => 'intersection' }, + '|' => {'Bignum' => 'bit_or', + 'Fixnum' => 'bit_or', + 'Array' => 'union', + 'TrueClass' => 'or', + 'FalseClass' => 'or', + 'NilClass' => 'or', + 'Set' => 'union' }, + '^' => {'Bignum' => 'bit_xor', + 'Fixnum' => 'bit_xor', + 'TrueClass' => 'xor', + 'FalseClass' => 'xor', + 'NilClass' => 'xor', + 'Set' => 'exclusion'}, + } + + EXCLUDED = %w[ + MSpecScript + MkSpec + MSpecOption + MSpecOptions + NameMap + SpecVersion + ] + + def initialize(filter=false) + @seen = {} + @filter = filter + end + + def exception?(name) + return false unless c = class_or_module(name) + c == Errno or c.ancestors.include? Exception + end + + def class_or_module(c) + const = Object.const_get(c, false) + filtered = @filter && EXCLUDED.include?(const.name) + return const if Module === const and !filtered + rescue NameError + end + + def namespace(mod, const) + return const.to_s if mod.nil? or %w[Object Class Module].include? mod + "#{mod}::#{const}" + end + + def map(hash, constants, mod=nil) + @seen = {} unless mod + + constants.each do |const| + name = namespace mod, const + m = class_or_module name + next unless m and !@seen[m] + @seen[m] = true + + ms = m.methods(false).map { |x| x.to_s } + hash["#{name}."] = ms.sort unless ms.empty? + + ms = m.public_instance_methods(false) + + m.protected_instance_methods(false) + ms.map! { |x| x.to_s } + hash["#{name}#"] = ms.sort unless ms.empty? + + map hash, m.constants(false), name + end + + hash + end + + def dir_name(c, base) + return File.join(base, 'exception') if exception? c + + c.split('::').inject(base) do |dir, name| + name.gsub!(/Class/, '') unless name == 'Class' + File.join dir, name.downcase + end + end + + def file_name(m, c) + if MAP.key?(m) + name = MAP[m].is_a?(Hash) ? MAP[m][c.split('::').last] || MAP[m][:default] : MAP[m] + else + name = m.gsub(/[?!=]/, '') + end + "#{name}_spec.rb" + end +end diff --git a/spec/mspec/lib/mspec/utils/options.rb b/spec/mspec/lib/mspec/utils/options.rb new file mode 100644 index 0000000000..122ef6e135 --- /dev/null +++ b/spec/mspec/lib/mspec/utils/options.rb @@ -0,0 +1,489 @@ +require 'mspec/version' + +MSPEC_HOME = File.expand_path('../../../..', __FILE__) + +class MSpecOption + attr_reader :short, :long, :arg, :description, :block + + def initialize(short, long, arg, description, block) + @short = short + @long = long + @arg = arg + @description = description + @block = block + end + + def arg? + @arg != nil + end + + def match?(opt) + opt == @short or opt == @long + end +end + +# MSpecOptions provides a parser for command line options. It also +# provides a composable set of options from which the runner scripts +# can select for their particular functionality. +class MSpecOptions + # Raised if incorrect or incomplete formats are passed to #on. + class OptionError < Exception; end + + # Raised if an unrecognized option is encountered. + class ParseError < Exception; end + + attr_accessor :config, :banner, :width, :options + + def initialize(banner="", width=30, config=nil) + @banner = banner + @config = config + @width = width + @options = [] + @doc = [] + @extra = [] + @on_extra = lambda { |x| + raise ParseError, "Unrecognized option: #{x}" if x[0] == ?- + @extra << x + } + + yield self if block_given? + end + + # Registers an option. Acceptable formats for arguments are: + # + # on "-a", "description" + # on "-a", "--abdc", "description" + # on "-a", "ARG", "description" + # on "--abdc", "ARG", "description" + # on "-a", "--abdc", "ARG", "description" + # + # If an block is passed, it will be invoked when the option is + # matched. Not passing a block is permitted, but nonsensical. + def on(*args, &block) + raise OptionError, "option and description are required" if args.size < 2 + + description = args.pop + short, long, argument = nil + args.each do |arg| + if arg[0] == ?- + if arg[1] == ?- + long = arg + else + short = arg + end + else + argument = arg + end + end + + add short, long, argument, description, block + end + + # Adds documentation text for an option and adds an +MSpecOption+ + # instance to the list of registered options. + def add(short, long, arg, description, block) + s = short ? short.dup : " " + s += (short ? ", " : " ") if long + doc " #{s}#{long} #{arg}".ljust(@width-1) + " #{description}" + @options << MSpecOption.new(short, long, arg, description, block) + end + + # Searches all registered options to find a match for +opt+. Returns + # +nil+ if no registered options match. + def match?(opt) + @options.find { |o| o.match? opt } + end + + # Processes an option. Calles the #on_extra block (or default) for + # unrecognized options. For registered options, possibly fetches an + # argument and invokes the option's block if it is not nil. + def process(argv, entry, opt, arg) + unless option = match?(opt) + @on_extra[entry] + else + if option.arg? + arg = argv.shift if arg.nil? + raise ParseError, "No argument provided for #{opt}" unless arg + option.block[arg] if option.block + else + option.block[] if option.block + end + end + option + end + + # Splits a string at +n+ characters into the +opt+ and the +rest+. + # The +arg+ is set to +nil+ if +rest+ is an empty string. + def split(str, n) + opt = str[0, n] + rest = str[n, str.size] + arg = rest == "" ? nil : rest + return opt, arg, rest + end + + # Parses an array of command line entries, calling blocks for + # registered options. + def parse(argv=ARGV) + argv = Array(argv).dup + + while entry = argv.shift + # collect everything that is not an option + if entry[0] != ?- or entry.size < 2 + @on_extra[entry] + next + end + + # this is a long option + if entry[1] == ?- + opt, arg = entry.split "=" + process argv, entry, opt, arg + next + end + + # disambiguate short option group from short option with argument + opt, arg, rest = split entry, 2 + + # process first option + option = process argv, entry, opt, arg + next unless option and !option.arg? + + # process the rest of the options + while rest.size > 0 + opt, arg, rest = split rest, 1 + opt = "-" + opt + option = process argv, opt, opt, arg + break if !option or option.arg? + end + end + + @extra + rescue ParseError => e + puts self + puts e + exit 1 + end + + # Adds a string of documentation text inline in the text generated + # from the options. See #on and #add. + def doc(str) + @doc << str + end + + # Convenience method for providing -v, --version options. + def version(version, &block) + show = block || lambda { puts "#{File.basename $0} #{version}"; exit } + on "-v", "--version", "Show version", &show + end + + # Convenience method for providing -h, --help options. + def help(&block) + help = block || lambda { puts self; exit 1 } + on "-h", "--help", "Show this message", &help + end + + # Stores a block that will be called with unrecognized options + def on_extra(&block) + @on_extra = block + end + + # Returns a string representation of the options and doc strings. + def to_s + @banner + "\n\n" + @doc.join("\n") + "\n" + end + + # The methods below provide groups of options that + # are composed by the particular runners to provide + # their functionality + + def configure(&block) + on("-B", "--config", "FILE", + "Load FILE containing configuration options", &block) + end + + def name + on("-n", "--name", "RUBY_NAME", + "Set the value of RUBY_NAME (used to determine the implementation)") do |n| + Object.const_set :RUBY_NAME, n + end + end + + def targets + on("-t", "--target", "TARGET", + "Implementation to run the specs, where TARGET is:") do |t| + case t + when 'r', 'ruby' + config[:target] = 'ruby' + when 'x', 'rubinius' + config[:target] = './bin/rbx' + when 'X', 'rbx' + config[:target] = 'rbx' + when 'j', 'jruby' + config[:target] = 'jruby' + when 'i','ironruby' + config[:target] = 'ir' + when 'm','maglev' + config[:target] = 'maglev-ruby' + when 't','topaz' + config[:target] = 'topaz' + when 'o','opal' + mspec_lib = File.expand_path('../../../', __FILE__) + config[:target] = "./bin/opal -syaml -sfileutils -rnodejs -rnodejs/require -rnodejs/yaml -rprocess -Derror -I#{mspec_lib} -I./lib/ -I. " + else + config[:target] = t + end + end + + doc "" + doc " r or ruby invokes ruby in PATH" + doc " x or rubinius invokes ./bin/rbx" + doc " X or rbx invokes rbx in PATH" + doc " j or jruby invokes jruby in PATH" + doc " i or ironruby invokes ir in PATH" + doc " m or maglev invokes maglev-ruby in PATH" + doc " t or topaz invokes topaz in PATH" + doc " o or opal invokes ./bin/opal with options" + doc " full path to EXE invokes EXE directly\n" + + on("-T", "--target-opt", "OPT", + "Pass OPT as a flag to the target implementation") do |t| + config[:flags] << t + end + on("-I", "--include", "DIR", + "Pass DIR through as the -I option to the target") do |d| + config[:loadpath] << "-I#{d}" + end + on("-r", "--require", "LIBRARY", + "Pass LIBRARY through as the -r option to the target") do |f| + config[:requires] << "-r#{f}" + end + end + + def formatters + on("-f", "--format", "FORMAT", + "Formatter for reporting, where FORMAT is one of:") do |o| + require 'mspec/runner/formatters' + case o + when 's', 'spec', 'specdoc' + config[:formatter] = SpecdocFormatter + when 'h', 'html' + config[:formatter] = HtmlFormatter + when 'd', 'dot', 'dotted' + config[:formatter] = DottedFormatter + when 'b', 'describe' + config[:formatter] = DescribeFormatter + when 'f', 'file' + config[:formatter] = FileFormatter + when 'u', 'unit', 'unitdiff' + config[:formatter] = UnitdiffFormatter + when 'm', 'summary' + config[:formatter] = SummaryFormatter + when 'a', '*', 'spin' + config[:formatter] = SpinnerFormatter + when 't', 'method' + config[:formatter] = MethodFormatter + when 'y', 'yaml' + config[:formatter] = YamlFormatter + when 'p', 'profile' + config[:formatter] = ProfileFormatter + when 'j', 'junit' + config[:formatter] = JUnitFormatter + else + abort "Unknown format: #{o}\n#{@parser}" unless File.exist?(o) + require File.expand_path(o) + if Object.const_defined?(:CUSTOM_MSPEC_FORMATTER) + config[:formatter] = CUSTOM_MSPEC_FORMATTER + else + abort "You must define CUSTOM_MSPEC_FORMATTER in your custom formatter file" + end + end + end + + doc "" + doc " s, spec, specdoc SpecdocFormatter" + doc " h, html, HtmlFormatter" + doc " d, dot, dotted DottedFormatter" + doc " f, file FileFormatter" + doc " u, unit, unitdiff UnitdiffFormatter" + doc " m, summary SummaryFormatter" + doc " a, *, spin SpinnerFormatter" + doc " t, method MethodFormatter" + doc " y, yaml YamlFormatter" + doc " p, profile ProfileFormatter" + doc " j, junit JUnitFormatter\n" + + on("-o", "--output", "FILE", + "Write formatter output to FILE") do |f| + config[:output] = f + end + end + + def filters + on("-e", "--example", "STR", + "Run examples with descriptions matching STR") do |o| + config[:includes] << o + end + on("-E", "--exclude", "STR", + "Exclude examples with descriptions matching STR") do |o| + config[:excludes] << o + end + on("-p", "--pattern", "PATTERN", + "Run examples with descriptions matching PATTERN") do |o| + config[:patterns] << Regexp.new(o) + end + on("-P", "--excl-pattern", "PATTERN", + "Exclude examples with descriptions matching PATTERN") do |o| + config[:xpatterns] << Regexp.new(o) + end + on("-g", "--tag", "TAG", + "Run examples with descriptions matching ones tagged with TAG") do |o| + config[:tags] << o + end + on("-G", "--excl-tag", "TAG", + "Exclude examples with descriptions matching ones tagged with TAG") do |o| + config[:xtags] << o + end + on("-w", "--profile", "FILE", + "Run examples for methods listed in the profile FILE") do |f| + config[:profiles] << f + end + on("-W", "--excl-profile", "FILE", + "Exclude examples for methods listed in the profile FILE") do |f| + config[:xprofiles] << f + end + end + + def chdir + on("-C", "--chdir", "DIR", + "Change the working directory to DIR before running specs") do |d| + Dir.chdir d + end + end + + def prefix + on("--prefix", "STR", "Prepend STR when resolving spec file names") do |p| + config[:prefix] = p + end + end + + def pretend + on("-Z", "--dry-run", + "Invoke formatters and other actions, but don't execute the specs") do + MSpec.register_mode :pretend + end + end + + def unguarded + on("--unguarded", "Turn off all guards") do + MSpec.register_mode :unguarded + end + on("--no-ruby_bug", "Turn off the ruby_bug guard") do + MSpec.register_mode :no_ruby_bug + end + end + + def randomize + on("-H", "--random", + "Randomize the list of spec files") do + MSpec.randomize + end + end + + def repeat + on("-R", "--repeat", "NUMBER", + "Repeatedly run an example NUMBER times") do |o| + MSpec.repeat = o.to_i + end + end + + def verbose + on("-V", "--verbose", "Output the name of each file processed") do + obj = Object.new + def obj.start + @width = MSpec.retrieve(:files).inject(0) { |max, f| f.size > max ? f.size : max } + end + def obj.load + file = MSpec.retrieve :file + print "\n#{file.ljust(@width)}" + end + MSpec.register :start, obj + MSpec.register :load, obj + end + + on("-m", "--marker", "MARKER", + "Output MARKER for each file processed") do |o| + obj = Object.new + obj.instance_variable_set :@marker, o + def obj.load + print @marker + end + MSpec.register :load, obj + end + end + + def interrupt + on("--int-spec", "Control-C interupts the current spec only") do + config[:abort] = false + end + end + + def verify + on("--report-on", "GUARD", "Report specs guarded by GUARD") do |g| + MSpec.register_mode :report_on + SpecGuard.guards << g.to_sym + end + on("-O", "--report", "Report guarded specs") do + MSpec.register_mode :report + end + on("-Y", "--verify", + "Verify that guarded specs pass and fail as expected") do + MSpec.register_mode :verify + end + end + + def action_filters + on("-K", "--action-tag", "TAG", + "Spec descriptions marked with TAG will trigger the specified action") do |o| + config[:atags] << o + end + on("-S", "--action-string", "STR", + "Spec descriptions matching STR will trigger the specified action") do |o| + config[:astrings] << o + end + end + + def actions + on("--spec-debug", + "Invoke the debugger when a spec description matches (see -K, -S)") do + config[:debugger] = true + end + end + + def debug + on("-d", "--debug", + "Set MSpec debugging flag for more verbose output") do + $MSPEC_DEBUG = true + end + end + + def all + # Generated with: + # puts File.read(__FILE__).scan(/def (\w+).*\n\s*on\(/) + configure {} + name + targets + formatters + filters + chdir + prefix + pretend + unguarded + randomize + repeat + verbose + interrupt + verify + action_filters + actions + debug + end +end diff --git a/spec/mspec/lib/mspec/utils/ruby_name.rb b/spec/mspec/lib/mspec/utils/ruby_name.rb new file mode 100644 index 0000000000..e381e387f6 --- /dev/null +++ b/spec/mspec/lib/mspec/utils/ruby_name.rb @@ -0,0 +1,8 @@ +unless Object.const_defined?(:RUBY_NAME) and RUBY_NAME + if Object.const_defined?(:RUBY_ENGINE) and RUBY_ENGINE + RUBY_NAME = RUBY_ENGINE + else + require 'rbconfig' + RUBY_NAME = RbConfig::CONFIG["RUBY_INSTALL_NAME"] || RbConfig::CONFIG["ruby_install_name"] + end +end diff --git a/spec/mspec/lib/mspec/utils/script.rb b/spec/mspec/lib/mspec/utils/script.rb new file mode 100644 index 0000000000..28be854a85 --- /dev/null +++ b/spec/mspec/lib/mspec/utils/script.rb @@ -0,0 +1,267 @@ +require 'mspec/guards/guard' +require 'mspec/utils/warnings' + +# MSpecScript provides a skeleton for all the MSpec runner scripts. + +class MSpecScript + # Returns the config object. Maintained at the class + # level to easily enable simple config files. See the + # class method +set+. + def self.config + @config ||= { + :path => ['.', 'spec'], + :config_ext => '.mspec' + } + end + + # Associates +value+ with +key+ in the config object. Enables + # simple config files of the form: + # + # class MSpecScript + # set :target, "ruby" + # set :files, ["one_spec.rb", "two_spec.rb"] + # end + def self.set(key, value) + config[key] = value + end + + # Gets the value of +key+ from the config object. Simplifies + # getting values in a config file: + # + # class MSpecScript + # set :a, 1 + # set :b, 2 + # set :c, get(:a) + get(:b) + # end + def self.get(key) + config[key] + end + + def initialize + config[:formatter] = nil + config[:includes] = [] + config[:excludes] = [] + config[:patterns] = [] + config[:xpatterns] = [] + config[:tags] = [] + config[:xtags] = [] + config[:profiles] = [] + config[:xprofiles] = [] + config[:atags] = [] + config[:astrings] = [] + config[:ltags] = [] + config[:abort] = true + @loaded = [] + end + + # Returns the config object maintained by the instance's class. + # See the class methods +set+ and +config+. + def config + MSpecScript.config + end + + # Returns +true+ if the file was located in +config[:path]+, + # possibly appending +config[:config_ext]. Returns +false+ + # otherwise. + def try_load(target) + names = [target] + unless target[-6..-1] == config[:config_ext] + names << target + config[:config_ext] + end + + names.each do |name| + config[:path].each do |dir| + file = File.expand_path name, dir + if @loaded.include?(file) + return true + elsif File.exist? file + value = Kernel.load(file) + @loaded << file + return value + end + end + end + + false + end + + def load(target) + try_load(target) or abort "Could not load config file #{target}" + end + + # Attempts to load a default config file. First tries to load + # 'default.mspec'. If that fails, attempts to load a config + # file name constructed from the value of RUBY_ENGINE and the + # first two numbers in RUBY_VERSION. For example, on MRI 1.8.6, + # the file name would be 'ruby.1.8.mspec'. + def load_default + try_load 'default.mspec' + + if Object.const_defined?(:RUBY_ENGINE) + engine = RUBY_ENGINE + else + engine = 'ruby' + end + try_load "#{engine}.#{SpecGuard.ruby_version}.mspec" + try_load "#{engine}.mspec" + end + + # Callback for enabling custom options. This version is a no-op. + # Provide an implementation specific version in a config file. + # Called by #options after the MSpec-provided options are added. + def custom_options(options) + options.doc " No custom options registered" + end + + # Registers all filters and actions. + def register + require 'mspec/runner/formatters/dotted' + require 'mspec/runner/formatters/spinner' + require 'mspec/runner/formatters/file' + require 'mspec/runner/filters' + + if config[:formatter].nil? + config[:formatter] = STDOUT.tty? ? SpinnerFormatter : @files.size < 50 ? DottedFormatter : FileFormatter + end + + if config[:formatter] + formatter = config[:formatter].new(config[:output]) + formatter.register + MSpec.store :formatter, formatter + end + + MatchFilter.new(:include, *config[:includes]).register unless config[:includes].empty? + MatchFilter.new(:exclude, *config[:excludes]).register unless config[:excludes].empty? + RegexpFilter.new(:include, *config[:patterns]).register unless config[:patterns].empty? + RegexpFilter.new(:exclude, *config[:xpatterns]).register unless config[:xpatterns].empty? + TagFilter.new(:include, *config[:tags]).register unless config[:tags].empty? + TagFilter.new(:exclude, *config[:xtags]).register unless config[:xtags].empty? + ProfileFilter.new(:include, *config[:profiles]).register unless config[:profiles].empty? + ProfileFilter.new(:exclude, *config[:xprofiles]).register unless config[:xprofiles].empty? + + DebugAction.new(config[:atags], config[:astrings]).register if config[:debugger] + + custom_register + end + + # Callback for enabling custom actions, etc. This version is a + # no-op. Provide an implementation specific version in a config + # file. Called by #register. + def custom_register + end + + # Sets up signal handlers. Only a handler for SIGINT is + # registered currently. + def signals + if config[:abort] + Signal.trap "INT" do + MSpec.actions :abort + puts "\nProcess aborted!" + exit! 1 + end + end + end + + # Attempts to resolve +partial+ as a file or directory name in the + # following order: + # + # 1. +partial+ + # 2. +partial+ + "_spec.rb" + # 3. File.join(config[:prefix], partial) + # 4. File.join(config[:prefix], partial + "_spec.rb") + # + # If it is a file name, returns the name as an entry in an array. + # If it is a directory, returns all *_spec.rb files in the + # directory and subdirectories. + # + # If unable to resolve +partial+, +Kernel.abort+ is called. + def entries(partial) + file = partial + "_spec.rb" + patterns = [partial, file] + if config[:prefix] + patterns << File.join(config[:prefix], partial) + patterns << File.join(config[:prefix], file) + end + + patterns.each do |pattern| + expanded = File.expand_path(pattern) + if File.file?(expanded) + return [expanded] + elsif File.directory?(expanded) + return Dir["#{expanded}/**/*_spec.rb"].sort + end + end + + abort "Could not find spec file #{partial}" + end + + # Resolves each entry in +list+ to a set of files. + # + # If the entry has a leading '^' character, the list of files + # is subtracted from the list of files accumulated to that point. + # + # If the entry has a leading ':' character, the corresponding + # key is looked up in the config object and the entries in the + # value retrieved are processed through #entries. + def files(list) + list.inject([]) do |files, item| + case item[0] + when ?^ + files -= entries(item[1..-1]) + when ?: + key = item[1..-1].to_sym + files += files(Array(config[key])) + else + files += entries(item) + end + files + end + end + + def files_from_patterns(patterns) + unless $0.end_with?("_spec.rb") + if patterns.empty? + patterns = config[:files] + end + if patterns.empty? and File.directory? "./spec" + patterns = ["spec/"] + end + if patterns.empty? + puts "No files specified." + exit 1 + end + end + files patterns + end + + def cores + require 'etc' + Etc.nprocessors + end + + def setup_env + ENV['MSPEC_RUNNER'] = '1' + + unless ENV['RUBY_EXE'] + ENV['RUBY_EXE'] = config[:target] if config[:target] + end + + unless ENV['RUBY_FLAGS'] + ENV['RUBY_FLAGS'] = config[:flags].join(" ") if config[:flags] + end + end + + # Instantiates an instance and calls the series of methods to + # invoke the script. + def self.main + script = new + script.load_default + script.try_load '~/.mspecrc' + script.options + script.signals + script.register + script.setup_env + require 'mspec' + script.run + end +end diff --git a/spec/mspec/lib/mspec/utils/version.rb b/spec/mspec/lib/mspec/utils/version.rb new file mode 100644 index 0000000000..787a76b053 --- /dev/null +++ b/spec/mspec/lib/mspec/utils/version.rb @@ -0,0 +1,52 @@ +class SpecVersion + # If beginning implementations have a problem with this include, we can + # manually implement the relational operators that are needed. + include Comparable + + # SpecVersion handles comparison correctly for the context by filling in + # missing version parts according to the value of +ceil+. If +ceil+ is + # +false+, 0 digits fill in missing version parts. If +ceil+ is +true+, 9 + # digits fill in missing parts. (See e.g. VersionGuard and BugGuard.) + def initialize(version, ceil = false) + @version = version + @ceil = ceil + @integer = nil + end + + def to_s + @version + end + + def to_str + to_s + end + + # Converts a string representation of a version major.minor.tiny + # to an integer representation so that comparisons can be made. For example, + # "2.2.10" < "2.2.2" would be false if compared as strings. + def to_i + unless @integer + major, minor, tiny = @version.split "." + if @ceil + tiny = 99 unless tiny + end + parts = [major, minor, tiny].map { |x| x.to_i } + @integer = ("1%02d%02d%02d" % parts).to_i + end + @integer + end + + def to_int + to_i + end + + def <=>(other) + if other.respond_to? :to_int + other = Integer other + else + other = SpecVersion.new(String(other)).to_i + end + + self.to_i <=> other + end +end diff --git a/spec/mspec/lib/mspec/utils/warnings.rb b/spec/mspec/lib/mspec/utils/warnings.rb new file mode 100644 index 0000000000..74c7f88a52 --- /dev/null +++ b/spec/mspec/lib/mspec/utils/warnings.rb @@ -0,0 +1,32 @@ +require 'mspec/guards/version' + +if RUBY_ENGINE == "ruby" and RUBY_VERSION >= "2.4.0" + ruby_version_is "2.4"..."2.5" do + # Kernel#warn does not delegate to Warning.warn in 2.4 + module Kernel + def warn(*messages) + return if $VERBOSE == nil or messages.empty? + msg = messages.join("\n") + msg += "\n" unless msg.end_with?("\n") + Warning.warn(msg) + end + private :warn + end + end + + def Warning.warn(message) + case message + when /constant ::(Fixnum|Bignum) is deprecated/ + when /\/(argf|io|stringio)\/.+(ARGF|IO)#(lines|chars|bytes|codepoints) is deprecated/ + when /Thread\.exclusive is deprecated.+\n.+thread\/exclusive_spec\.rb/ + when /hash\/shared\/index\.rb:\d+: warning: Hash#index is deprecated; use Hash#key/ + when /env\/shared\/key\.rb:\d+: warning: ENV\.index is deprecated; use ENV\.key/ + when /exponent(_spec)?\.rb:\d+: warning: in a\*\*b, b may be too big/ + when /enumerator\/(new|initialize_spec)\.rb:\d+: warning: Enumerator\.new without a block is deprecated/ + else + $stderr.write message + end + end +else + $VERBOSE = nil unless ENV['OUTPUT_WARNINGS'] +end diff --git a/spec/mspec/lib/mspec/version.rb b/spec/mspec/lib/mspec/version.rb new file mode 100644 index 0000000000..9126f5366e --- /dev/null +++ b/spec/mspec/lib/mspec/version.rb @@ -0,0 +1,5 @@ +require 'mspec/utils/version' + +module MSpec + VERSION = SpecVersion.new "1.8.0" +end diff --git a/spec/mspec/mspec.gemspec b/spec/mspec/mspec.gemspec new file mode 100644 index 0000000000..428067dfd3 --- /dev/null +++ b/spec/mspec/mspec.gemspec @@ -0,0 +1,40 @@ +# -*- encoding: utf-8 -*- +$:.unshift File.expand_path('../lib', __FILE__) +require 'mspec/version' + +Gem::Specification.new do |gem| + gem.name = "mspec" + gem.version = MSpec::VERSION.to_s + gem.authors = ["Brian Shirai"] + gem.email = ["bshirai@engineyard.com"] + gem.homepage = "http://rubyspec.org" + + gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) unless File.extname(f) == ".bat" }.compact + gem.files = `git ls-files`.split("\n") + gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") + gem.require_paths = ["lib"] + gem.description = <<-EOD +MSpec is a specialized framework for RubySpec. + EOD + gem.summary = <<-EOS +MSpec is a specialized framework that is syntax-compatible +with RSpec for basic things like describe, it blocks and +before, after actions. + +MSpec contains additional features that assist in writing +the RubySpecs used by multiple Ruby implementations. Also, +MSpec attempts to use the simplest Ruby language features +so that beginning Ruby implementations can run it. + EOS + gem.has_rdoc = true + gem.extra_rdoc_files = %w[ README.md LICENSE ] + gem.rubygems_version = %q{1.3.5} + gem.rubyforge_project = 'http://rubyforge.org/projects/mspec' + + gem.rdoc_options << '--title' << 'MSpec Gem' << + '--main' << 'README.md' << + '--line-numbers' + + gem.add_development_dependency "rake", "~> 10.0" + gem.add_development_dependency "rspec", "~> 2.14.1" +end diff --git a/spec/mspec/spec/commands/fixtures/four.txt b/spec/mspec/spec/commands/fixtures/four.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/spec/mspec/spec/commands/fixtures/level2/three_spec.rb b/spec/mspec/spec/commands/fixtures/level2/three_spec.rb new file mode 100644 index 0000000000..e69de29bb2 diff --git a/spec/mspec/spec/commands/fixtures/one_spec.rb b/spec/mspec/spec/commands/fixtures/one_spec.rb new file mode 100644 index 0000000000..e69de29bb2 diff --git a/spec/mspec/spec/commands/fixtures/three.rb b/spec/mspec/spec/commands/fixtures/three.rb new file mode 100644 index 0000000000..e69de29bb2 diff --git a/spec/mspec/spec/commands/fixtures/two_spec.rb b/spec/mspec/spec/commands/fixtures/two_spec.rb new file mode 100644 index 0000000000..e69de29bb2 diff --git a/spec/mspec/spec/commands/mkspec_spec.rb b/spec/mspec/spec/commands/mkspec_spec.rb new file mode 100644 index 0000000000..ab3410af50 --- /dev/null +++ b/spec/mspec/spec/commands/mkspec_spec.rb @@ -0,0 +1,363 @@ +require 'spec_helper' +require 'mspec/commands/mkspec' + + +describe "The -c, --constant CONSTANT option" do + before :each do + @options = MSpecOptions.new + MSpecOptions.stub(:new).and_return(@options) + @script = MkSpec.new + @config = @script.config + end + + it "is enabled by #options" do + @options.stub(:on) + @options.should_receive(:on).with("-c", "--constant", "CONSTANT", + an_instance_of(String)) + @script.options [] + end + + it "adds CONSTANT to the list of constants" do + ["-c", "--constant"].each do |opt| + @config[:constants] = [] + @script.options [opt, "Object"] + @config[:constants].should include("Object") + end + end +end + +describe "The -b, --base DIR option" do + before :each do + @options = MSpecOptions.new + MSpecOptions.stub(:new).and_return(@options) + @script = MkSpec.new + @config = @script.config + end + + it "is enabled by #options" do + @options.stub(:on) + @options.should_receive(:on).with("-b", "--base", "DIR", + an_instance_of(String)) + @script.options + end + + it "sets the base directory relative to which the spec directories are created" do + ["-b", "--base"].each do |opt| + @config[:base] = nil + @script.options [opt, "superspec"] + @config[:base].should == File.expand_path("superspec") + end + end +end + +describe "The -r, --require LIBRARY option" do + before :each do + @options = MSpecOptions.new + MSpecOptions.stub(:new).and_return(@options) + @script = MkSpec.new + @config = @script.config + end + + it "is enabled by #options" do + @options.stub(:on) + @options.should_receive(:on).with("-r", "--require", "LIBRARY", + an_instance_of(String)) + @script.options + end + + it "adds CONSTANT to the list of constants" do + ["-r", "--require"].each do |opt| + @config[:requires] = [] + @script.options [opt, "libspec"] + @config[:requires].should include("libspec") + end + end +end + +describe "The -V, --version-guard VERSION option" do + before :each do + @options = MSpecOptions.new + MSpecOptions.stub(:new).and_return(@options) + @script = MkSpec.new + @config = @script.config + end + + it "is enabled by #options" do + @options.stub(:on) + @options.should_receive(:on).with("-V", "--version-guard", "VERSION", + an_instance_of(String)) + @script.options + end + + it "sets the version for the ruby_version_is guards to VERSION" do + ["-r", "--require"].each do |opt| + @config[:requires] = [] + @script.options [opt, "libspec"] + @config[:requires].should include("libspec") + end + end +end + +describe MkSpec, "#options" do + before :each do + @options = MSpecOptions.new + MSpecOptions.stub(:new).and_return(@options) + @script = MkSpec.new + end + + it "parses the command line options" do + @options.should_receive(:parse).with(["--this", "and", "--that"]) + @script.options ["--this", "and", "--that"] + end + + it "parses ARGV unless passed other options" do + @options.should_receive(:parse).with(ARGV) + @script.options + end + + it "prints help and exits if passed an unrecognized option" do + @options.should_receive(:raise).with(MSpecOptions::ParseError, an_instance_of(String)) + @options.stub(:puts) + @options.stub(:exit) + @script.options "--iunknown" + end +end + +describe MkSpec, "#create_directory" do + before :each do + @script = MkSpec.new + @script.config[:base] = "spec" + end + + it "prints a warning if a file with the directory name exists" do + File.should_receive(:exist?).and_return(true) + File.should_receive(:directory?).and_return(false) + FileUtils.should_not_receive(:mkdir_p) + @script.should_receive(:puts).with("spec/class already exists and is not a directory.") + @script.create_directory("Class").should == nil + end + + it "does nothing if the directory already exists" do + File.should_receive(:exist?).and_return(true) + File.should_receive(:directory?).and_return(true) + FileUtils.should_not_receive(:mkdir_p) + @script.create_directory("Class").should == "spec/class" + end + + it "creates the directory if it does not exist" do + File.should_receive(:exist?).and_return(false) + @script.should_receive(:mkdir_p).with("spec/class") + @script.create_directory("Class").should == "spec/class" + end + + it "creates the directory for a namespaced module if it does not exist" do + File.should_receive(:exist?).and_return(false) + @script.should_receive(:mkdir_p).with("spec/struct/tms") + @script.create_directory("Struct::Tms").should == "spec/struct/tms" + end +end + +describe MkSpec, "#write_requires" do + before :each do + @script = MkSpec.new + @script.config[:base] = "spec" + + @file = double("file") + File.stub(:open).and_yield(@file) + end + + it "writes the spec_helper require line" do + @file.should_receive(:puts).with("require File.expand_path('../../../../spec_helper', __FILE__)") + @script.write_requires("spec/core/tcejbo", "spec/core/tcejbo/inspect_spec.rb") + end + + it "writes require lines for each library specified on the command line" do + @file.stub(:puts) + @file.should_receive(:puts).with("require File.expand_path('../../../../spec_helper', __FILE__)") + @file.should_receive(:puts).with("require 'complex'") + @script.config[:requires] << 'complex' + @script.write_requires("spec/core/tcejbo", "spec/core/tcejbo/inspect_spec.rb") + end +end + +describe MkSpec, "#write_spec" do + before :each do + @file = IOStub.new + File.stub(:open).and_yield(@file) + + @script = MkSpec.new + @script.stub(:puts) + + @response = double("system command response") + @response.stub(:include?).and_return(false) + @script.stub(:`).and_return(@response) + end + + it "checks if specs exist for the method if the spec file exists" do + name = Regexp.escape(@script.ruby) + @script.should_receive(:`).with( + %r"#{name} #{MSPEC_HOME}/bin/mspec-run --dry-run --unguarded -fs -e 'Object#inspect' spec/core/tcejbo/inspect_spec.rb") + @script.write_spec("spec/core/tcejbo/inspect_spec.rb", "Object#inspect", true) + end + + it "checks for the method name in the spec file output" do + @response.should_receive(:include?).with("Array#[]=") + @script.write_spec("spec/core/yarra/element_set_spec.rb", "Array#[]=", true) + end + + it "returns nil if the spec file exists and contains a spec for the method" do + @response.stub(:include?).and_return(true) + @script.write_spec("spec/core/tcejbo/inspect_spec.rb", "Object#inspect", true).should == nil + end + + it "does not print the spec file name if it exists and contains a spec for the method" do + @response.stub(:include?).and_return(true) + @script.should_not_receive(:puts) + @script.write_spec("spec/core/tcejbo/inspect_spec.rb", "Object#inspect", true) + end + + it "prints the spec file name if a template spec is written" do + @script.should_receive(:puts).with("spec/core/tcejbo/inspect_spec.rb") + @script.write_spec("spec/core/tcejbo/inspect_spec.rb", "Object#inspect", true) + end + + it "writes a template spec to the file if the spec file does not exist" do + @file.should_receive(:puts).twice + @script.should_receive(:puts).with("spec/core/tcejbo/inspect_spec.rb") + @script.write_spec("spec/core/tcejbo/inspect_spec.rb", "Object#inspect", false) + end + + it "writes a template spec to the file if it exists but contains no spec for the method" do + @response.should_receive(:include?).and_return(false) + @file.should_receive(:puts).twice + @script.should_receive(:puts).with("spec/core/tcejbo/inspect_spec.rb") + @script.write_spec("spec/core/tcejbo/inspect_spec.rb", "Object#inspect", true) + end + + it "writes a template spec" do + @script.write_spec("spec/core/tcejbo/inspect_spec.rb", "Object#inspect", true) + @file.should == < ["run"]}) + @script.should_receive(:create_file).with("spec/mkspec", "MkSpec", "run", "MkSpec#run") + @script.run + end +end + +describe MkSpec, ".main" do + before :each do + @script = double("MkSpec").as_null_object + MkSpec.stub(:new).and_return(@script) + end + + it "sets MSPEC_RUNNER = '1' in the environment" do + ENV["MSPEC_RUNNER"] = "0" + MkSpec.main + ENV["MSPEC_RUNNER"].should == "1" + end + + it "creates an instance of MSpecScript" do + MkSpec.should_receive(:new).and_return(@script) + MkSpec.main + end + + it "calls the #options method on the script" do + @script.should_receive(:options) + MkSpec.main + end + + it "calls the #run method on the script" do + @script.should_receive(:run) + MkSpec.main + end +end diff --git a/spec/mspec/spec/commands/mspec_ci_spec.rb b/spec/mspec/spec/commands/mspec_ci_spec.rb new file mode 100644 index 0000000000..1e8949b0d3 --- /dev/null +++ b/spec/mspec/spec/commands/mspec_ci_spec.rb @@ -0,0 +1,155 @@ +require 'spec_helper' +require 'mspec/runner/mspec' +require 'mspec/runner/filters/tag' +require 'mspec/commands/mspec-ci' + +describe MSpecCI, "#options" do + before :each do + @options, @config = new_option + MSpecOptions.stub(:new).and_return(@options) + + @script = MSpecCI.new + @script.stub(:config).and_return(@config) + @script.stub(:files).and_return([]) + end + + it "enables the chdir option" do + @options.should_receive(:chdir) + @script.options + end + + it "enables the prefix option" do + @options.should_receive(:prefix) + @script.options + end + + it "enables the config option" do + @options.should_receive(:configure) + @script.options + end + + it "provides a custom action (block) to the config option" do + @script.should_receive(:load).with("cfg.mspec") + @script.options ["-B", "cfg.mspec"] + end + + it "enables the name option" do + @options.should_receive(:name) + @script.options + end + + it "enables the dry run option" do + @options.should_receive(:pretend) + @script.options + end + + it "enables the unguarded option" do + @options.should_receive(:unguarded) + @script.options + end + + it "enables the interrupt single specs option" do + @options.should_receive(:interrupt) + @script.options + end + + it "enables the formatter options" do + @options.should_receive(:formatters) + @script.options + end + + it "enables the verbose option" do + @options.should_receive(:verbose) + @script.options + end + + it "enables the action options" do + @options.should_receive(:actions) + @script.options + end + + it "enables the action filter options" do + @options.should_receive(:action_filters) + @script.options + end + + it "enables the version option" do + @options.should_receive(:version) + @script.options + end + + it "enables the help option" do + @options.should_receive(:help) + @script.options + end + + it "calls #custom_options" do + @script.should_receive(:custom_options).with(@options) + @script.options + end +end + +describe MSpecCI, "#run" do + before :each do + MSpec.stub(:process) + + @filter = double("TagFilter") + TagFilter.stub(:new).and_return(@filter) + @filter.stub(:register) + + @tags = ["fails", "critical", "unstable", "incomplete", "unsupported"] + + @config = { :ci_files => ["one", "two"] } + @script = MSpecCI.new + @script.stub(:exit) + @script.stub(:config).and_return(@config) + @script.stub(:files).and_return(["one", "two"]) + @script.options + end + + it "registers the tags patterns" do + @config[:tags_patterns] = [/spec/, "tags"] + MSpec.should_receive(:register_tags_patterns).with([/spec/, "tags"]) + @script.run + end + + it "registers the files to process" do + MSpec.should_receive(:register_files).with(["one", "two"]) + @script.run + end + + it "registers a tag filter for 'fails', 'unstable', 'incomplete', 'critical', 'unsupported'" do + filter = double("fails filter") + TagFilter.should_receive(:new).with(:exclude, *@tags).and_return(filter) + filter.should_receive(:register) + @script.run + end + + it "registers an additional exclude tag specified by :ci_xtags" do + @config[:ci_xtags] = "windows" + filter = double("fails filter") + TagFilter.should_receive(:new).with(:exclude, *(@tags + ["windows"])).and_return(filter) + filter.should_receive(:register) + @script.run + end + + it "registers additional exclude tags specified by a :ci_xtags array" do + @config[:ci_xtags] = ["windows", "windoze"] + filter = double("fails filter") + TagFilter.should_receive(:new).with(:exclude, + *(@tags + ["windows", "windoze"])).and_return(filter) + filter.should_receive(:register) + @script.run + end + + it "processes the files" do + MSpec.should_receive(:process) + @script.run + end + + it "exits with the exit code registered with MSpec" do + MSpec.stub(:exit_code).and_return(7) + @script.should_receive(:exit).with(7) + @script.run + end +end diff --git a/spec/mspec/spec/commands/mspec_run_spec.rb b/spec/mspec/spec/commands/mspec_run_spec.rb new file mode 100644 index 0000000000..4d350cdc02 --- /dev/null +++ b/spec/mspec/spec/commands/mspec_run_spec.rb @@ -0,0 +1,185 @@ +require 'spec_helper' +require 'mspec/runner/mspec' +require 'mspec/commands/mspec-run' + +one_spec = File.expand_path(File.dirname(__FILE__)) + '/fixtures/one_spec.rb' +two_spec = File.expand_path(File.dirname(__FILE__)) + '/fixtures/two_spec.rb' + +describe MSpecRun, ".new" do + before :each do + @script = MSpecRun.new + end + + it "sets config[:files] to an empty list" do + @script.config[:files].should == [] + end +end + +describe MSpecRun, "#options" do + before :each do + @stdout, $stdout = $stdout, IOStub.new + + @argv = [one_spec, two_spec] + @options, @config = new_option + MSpecOptions.stub(:new).and_return(@options) + + @script = MSpecRun.new + @script.stub(:config).and_return(@config) + end + + after :each do + $stdout = @stdout + end + + it "enables the filter options" do + @options.should_receive(:filters) + @script.options @argv + end + + it "enables the chdir option" do + @options.should_receive(:chdir) + @script.options @argv + end + + it "enables the prefix option" do + @options.should_receive(:prefix) + @script.options @argv + end + + it "enables the configure option" do + @options.should_receive(:configure) + @script.options @argv + end + + it "provides a custom action (block) to the config option" do + @script.should_receive(:load).with("cfg.mspec") + @script.options ["-B", "cfg.mspec", one_spec] + end + + it "enables the name option" do + @options.should_receive(:name) + @script.options @argv + end + + it "enables the randomize option to runs specs in random order" do + @options.should_receive(:randomize) + @script.options @argv + end + + it "enables the dry run option" do + @options.should_receive(:pretend) + @script.options @argv + end + + it "enables the unguarded option" do + @options.should_receive(:unguarded) + @script.options @argv + end + + it "enables the interrupt single specs option" do + @options.should_receive(:interrupt) + @script.options @argv + end + + it "enables the formatter options" do + @options.should_receive(:formatters) + @script.options @argv + end + + it "enables the verbose option" do + @options.should_receive(:verbose) + @script.options @argv + end + + it "enables the verify options" do + @options.should_receive(:verify) + @script.options @argv + end + + it "enables the action options" do + @options.should_receive(:actions) + @script.options @argv + end + + it "enables the action filter options" do + @options.should_receive(:action_filters) + @script.options @argv + end + + it "enables the version option" do + @options.should_receive(:version) + @script.options @argv + end + + it "enables the help option" do + @options.should_receive(:help) + @script.options @argv + end + + it "exits if there are no files to process and './spec' is not a directory" do + File.should_receive(:directory?).with("./spec").and_return(false) + @options.should_receive(:parse).and_return([]) + @script.should_receive(:exit) + @script.options + $stdout.should include "No files specified" + end + + it "process 'spec/' if it is a directory and no files were specified" do + File.should_receive(:directory?).with("./spec").and_return(true) + @options.should_receive(:parse).and_return([]) + @script.should_receive(:files).with(["spec/"]) + @script.options + end + + it "calls #custom_options" do + @script.should_receive(:custom_options).with(@options) + @script.options @argv + end +end + +describe MSpecRun, "#run" do + before :each do + @script = MSpecRun.new + @script.stub(:exit) + @spec_dir = File.expand_path(File.dirname(__FILE__)+"/fixtures") + @file_patterns = [ + @spec_dir+"/level2", + @spec_dir+"/one_spec.rb", + @spec_dir+"/two_spec.rb"] + @files = [ + @spec_dir+"/level2/three_spec.rb", + @spec_dir+"/one_spec.rb", + @spec_dir+"/two_spec.rb"] + @script.options @file_patterns + MSpec.stub :process + end + + it "registers the tags patterns" do + @script.config[:tags_patterns] = [/spec/, "tags"] + MSpec.should_receive(:register_tags_patterns).with([/spec/, "tags"]) + @script.run + end + + it "registers the files to process" do + MSpec.should_receive(:register_files).with(@files) + @script.run + end + + it "uses config[:files] if no files are given on the command line" do + @script.config[:files] = @file_patterns + MSpec.should_receive(:register_files).with(@files) + @script.options [] + @script.run + end + + it "processes the files" do + MSpec.should_receive(:process) + @script.run + end + + it "exits with the exit code registered with MSpec" do + MSpec.stub(:exit_code).and_return(7) + @script.should_receive(:exit).with(7) + @script.run + end +end diff --git a/spec/mspec/spec/commands/mspec_spec.rb b/spec/mspec/spec/commands/mspec_spec.rb new file mode 100644 index 0000000000..8b8b8fcc41 --- /dev/null +++ b/spec/mspec/spec/commands/mspec_spec.rb @@ -0,0 +1,215 @@ +require 'spec_helper' +require 'yaml' +require 'mspec/commands/mspec' + +describe MSpecMain, "#options" do + before :each do + @options, @config = new_option + MSpecOptions.stub(:new).and_return(@options) + + @script = MSpecMain.new + @script.stub(:config).and_return(@config) + @script.stub(:load) + end + + it "enables the configure option" do + @options.should_receive(:configure) + @script.options + end + + it "provides a custom action (block) to the config option" do + @script.options ["-B", "config"] + @config[:options].should include("-B", "config") + end + + it "loads the file specified by the config option" do + @script.should_receive(:load).with("config") + @script.options ["-B", "config"] + end + + it "enables the target options" do + @options.should_receive(:targets) + @script.options + end + + it "sets config[:options] to all argv entries that are not registered options" do + @options.on "-X", "--exclude", "ARG", "description" + @script.options [".", "-G", "fail", "-X", "ARG", "--list", "unstable", "some/file.rb"] + @config[:options].should == [".", "-G", "fail", "--list", "unstable", "some/file.rb"] + end + + it "calls #custom_options" do + @script.should_receive(:custom_options).with(@options) + @script.options + end +end + +describe MSpecMain, "#run" do + before :each do + @options, @config = new_option + MSpecOptions.stub(:new).and_return(@options) + @script = MSpecMain.new + @script.stub(:config).and_return(@config) + @script.stub(:exec) + @err = $stderr + $stderr = IOStub.new + end + + after :each do + $stderr = @err + end + + it "uses exec to invoke the runner script" do + @script.should_receive(:exec).with("ruby", "#{MSPEC_HOME}/bin/mspec-run") + @script.options [] + @script.run + end + + it "shows the command line on stderr" do + @script.should_receive(:exec).with("ruby", "#{MSPEC_HOME}/bin/mspec-run") + @script.options [] + @script.run + $stderr.to_s.should == "$ ruby #{Dir.pwd}/bin/mspec-run\n" + end + + it "adds config[:launch] to the exec options" do + @script.should_receive(:exec).with("ruby", + "-Xlaunch.option", "#{MSPEC_HOME}/bin/mspec-run") + @config[:launch] << "-Xlaunch.option" + @script.options [] + @script.run + $stderr.to_s.should == "$ ruby -Xlaunch.option #{Dir.pwd}/bin/mspec-run\n" + end + + it "calls #multi_exec if the command is 'ci' and the multi option is passed" do + @script.should_receive(:multi_exec).and_return do |argv| + argv.should == ["ruby", "#{MSPEC_HOME}/bin/mspec-ci", "-fy"] + end + @script.options ["ci", "-j"] + lambda do + @script.run + end.should raise_error(SystemExit) + end +end + +describe "The --warnings option" do + before :each do + @options, @config = new_option + MSpecOptions.stub(:new).and_return(@options) + @script = MSpecMain.new + @script.stub(:config).and_return(@config) + end + + it "is enabled by #options" do + @options.stub(:on) + @options.should_receive(:on).with("--warnings", an_instance_of(String)) + @script.options + end + + it "sets flags to -w" do + @config[:flags] = [] + @script.options ["--warnings"] + @config[:flags].should include("-w") + end + + it "set OUTPUT_WARNINGS = '1' in the environment" do + ENV['OUTPUT_WARNINGS'] = '0' + @script.options ["--warnings"] + ENV['OUTPUT_WARNINGS'].should == '1' + end +end + +describe "The -j, --multi option" do + before :each do + @options, @config = new_option + MSpecOptions.stub(:new).and_return(@options) + @script = MSpecMain.new + @script.stub(:config).and_return(@config) + end + + it "is enabled by #options" do + @options.stub(:on) + @options.should_receive(:on).with("-j", "--multi", an_instance_of(String)) + @script.options + end + + it "sets the multiple process option" do + ["-j", "--multi"].each do |opt| + @config[:multi] = nil + @script.options [opt] + @config[:multi].should == true + end + end + + it "sets the formatter to YamlFormatter" do + ["-j", "--multi"].each do |opt| + @config[:options] = [] + @script.options [opt] + @config[:options].should include("-fy") + end + end +end + +describe "The -h, --help option" do + before :each do + @options, @config = new_option + MSpecOptions.stub(:new).and_return(@options) + @script = MSpecMain.new + @script.stub(:config).and_return(@config) + end + + it "is enabled by #options" do + @options.stub(:on) + @options.should_receive(:on).with("-h", "--help", an_instance_of(String)) + @script.options + end + + it "passes the option to the subscript" do + ["-h", "--help"].each do |opt| + @config[:options] = [] + @script.options ["ci", opt] + @config[:options].sort.should == ["-h"] + end + end + + it "prints help and exits" do + @script.should_receive(:puts).twice + @script.should_receive(:exit).twice + ["-h", "--help"].each do |opt| + @script.options [opt] + end + end +end + +describe "The -v, --version option" do + before :each do + @options, @config = new_option + MSpecOptions.stub(:new).and_return(@options) + @script = MSpecMain.new + @script.stub(:config).and_return(@config) + end + + it "is enabled by #options" do + @options.stub(:on) + @options.should_receive(:on).with("-v", "--version", an_instance_of(String)) + @script.options + end + + it "passes the option to the subscripts" do + ["-v", "--version"].each do |opt| + @config[:options] = [] + @script.options ["ci", opt] + @config[:options].sort.should == ["-v"] + end + end + + it "prints the version and exits if no subscript is invoked" do + @config[:command] = nil + File.stub(:basename).and_return("mspec") + @script.should_receive(:puts).twice.with("mspec #{MSpec::VERSION}") + @script.should_receive(:exit).twice + ["-v", "--version"].each do |opt| + @script.options [opt] + end + end +end diff --git a/spec/mspec/spec/commands/mspec_tag_spec.rb b/spec/mspec/spec/commands/mspec_tag_spec.rb new file mode 100644 index 0000000000..3c2e94db52 --- /dev/null +++ b/spec/mspec/spec/commands/mspec_tag_spec.rb @@ -0,0 +1,419 @@ +require 'spec_helper' +require 'mspec/runner/mspec' +require 'mspec/commands/mspec-tag' +require 'mspec/runner/actions/tag' +require 'mspec/runner/actions/taglist' +require 'mspec/runner/actions/tagpurge' + +one_spec = File.expand_path(File.dirname(__FILE__)) + '/fixtures/one_spec.rb' +two_spec = File.expand_path(File.dirname(__FILE__)) + '/fixtures/two_spec.rb' + +describe MSpecTag, ".new" do + before :each do + @script = MSpecTag.new + end + + it "sets config[:ltags] to an empty list" do + @script.config[:ltags].should == [] + end + + it "sets config[:tagger] to :add" do + @script.config[:tagger] = :add + end + + it "sets config[:tag] to 'fails:'" do + @script.config[:tag] = 'fails:' + end + + it "sets config[:outcome] to :fail" do + @script.config[:outcome] = :fail + end +end + +describe MSpecTag, "#options" do + before :each do + @stdout, $stdout = $stdout, IOStub.new + + @argv = [one_spec, two_spec] + @options, @config = new_option + MSpecOptions.stub(:new).and_return(@options) + + @script = MSpecTag.new + @script.stub(:config).and_return(@config) + end + + after :each do + $stdout = @stdout + end + + it "enables the filter options" do + @options.should_receive(:filters) + @script.options @argv + end + + it "enables the configure option" do + @options.should_receive(:configure) + @script.options @argv + end + + it "provides a custom action (block) to the config option" do + @script.should_receive(:load).with("cfg.mspec") + @script.options ["-B", "cfg.mspec", one_spec] + end + + it "enables the name option" do + @options.should_receive(:name) + @script.options @argv + end + + it "enables the dry run option" do + @options.should_receive(:pretend) + @script.options @argv + end + + it "enables the unguarded option" do + @options.should_receive(:unguarded) + @script.options @argv + end + + it "enables the interrupt single specs option" do + @options.should_receive(:interrupt) + @script.options @argv + end + + it "enables the formatter options" do + @options.should_receive(:formatters) + @script.options @argv + end + + it "enables the verbose option" do + @options.should_receive(:verbose) + @script.options @argv + end + + it "enables the version option" do + @options.should_receive(:version) + @script.options @argv + end + + it "enables the help option" do + @options.should_receive(:help) + @script.options @argv + end + + it "calls #custom_options" do + @script.should_receive(:custom_options).with(@options) + @script.options @argv + end + + it "exits if there are no files to process" do + @options.should_receive(:parse).and_return([]) + @script.should_receive(:exit) + @script.options + $stdout.should include "No files specified" + end +end + +describe MSpecTag, "options" do + before :each do + @options, @config = new_option + MSpecOptions.stub(:new).and_return(@options) + @script = MSpecTag.new + @script.stub(:config).and_return(@config) + end + + describe "-N, --add TAG" do + it "is enabled with #options" do + @options.stub(:on) + @options.should_receive(:on).with("-N", "--add", "TAG", an_instance_of(String)) + @script.options [one_spec] + end + + it "sets the mode to :add and sets the tag to TAG" do + ["-N", "--add"].each do |opt| + @config[:tagger] = nil + @config[:tag] = nil + @script.options [opt, "taggit", one_spec] + @config[:tagger].should == :add + @config[:tag].should == "taggit:" + end + end + end + + describe "-R, --del TAG" do + it "is enabled with #options" do + @options.stub(:on) + @options.should_receive(:on).with("-R", "--del", "TAG", + an_instance_of(String)) + @script.options [one_spec] + end + + it "it sets the mode to :del, the tag to TAG, and the outcome to :pass" do + ["-R", "--del"].each do |opt| + @config[:tagger] = nil + @config[:tag] = nil + @config[:outcome] = nil + @script.options [opt, "taggit", one_spec] + @config[:tagger].should == :del + @config[:tag].should == "taggit:" + @config[:outcome].should == :pass + end + end + end + + describe "-Q, --pass" do + it "is enabled with #options" do + @options.stub(:on) + @options.should_receive(:on).with("-Q", "--pass", an_instance_of(String)) + @script.options [one_spec] + end + + it "sets the outcome to :pass" do + ["-Q", "--pass"].each do |opt| + @config[:outcome] = nil + @script.options [opt, one_spec] + @config[:outcome].should == :pass + end + end + end + + describe "-F, --fail" do + it "is enabled with #options" do + @options.stub(:on) + @options.should_receive(:on).with("-F", "--fail", an_instance_of(String)) + @script.options [one_spec] + end + + it "sets the outcome to :fail" do + ["-F", "--fail"].each do |opt| + @config[:outcome] = nil + @script.options [opt, one_spec] + @config[:outcome].should == :fail + end + end + end + + describe "-L, --all" do + it "is enabled with #options" do + @options.stub(:on) + @options.should_receive(:on).with("-L", "--all", an_instance_of(String)) + @script.options [one_spec] + end + + it "sets the outcome to :all" do + ["-L", "--all"].each do |opt| + @config[:outcome] = nil + @script.options [opt, one_spec] + @config[:outcome].should == :all + end + end + end + + describe "--list TAG" do + it "is enabled with #options" do + @options.stub(:on) + @options.should_receive(:on).with("--list", "TAG", an_instance_of(String)) + @script.options [one_spec] + end + + it "sets the mode to :list" do + @config[:tagger] = nil + @script.options ["--list", "TAG", one_spec] + @config[:tagger].should == :list + end + + it "sets ltags to include TAG" do + @config[:tag] = nil + @script.options ["--list", "TAG", one_spec] + @config[:ltags].should == ["TAG"] + end + end + + describe "--list-all" do + it "is enabled with #options" do + @options.stub(:on) + @options.should_receive(:on).with("--list-all", an_instance_of(String)) + @script.options [one_spec] + end + + it "sets the mode to :list_all" do + @config[:tagger] = nil + @script.options ["--list-all", one_spec] + @config[:tagger].should == :list_all + end + end + + describe "--purge" do + it "is enabled with #options" do + @options.stub(:on) + @options.should_receive(:on).with("--purge", an_instance_of(String)) + @script.options [one_spec] + end + + it "sets the mode to :purge" do + @config[:tagger] = nil + @script.options ["--purge", one_spec] + @config[:tagger].should == :purge + end + end +end + +describe MSpecTag, "#run" do + before :each do + MSpec.stub(:process) + + options = double("MSpecOptions").as_null_object + options.stub(:parse).and_return(["one", "two"]) + MSpecOptions.stub(:new).and_return(options) + + @config = { } + @script = MSpecTag.new + @script.stub(:exit) + @script.stub(:config).and_return(@config) + @script.stub(:files).and_return(["one", "two"]) + @script.options + end + + it "registers the tags patterns" do + @config[:tags_patterns] = [/spec/, "tags"] + MSpec.should_receive(:register_tags_patterns).with([/spec/, "tags"]) + @script.run + end + + it "registers the files to process" do + MSpec.should_receive(:register_files).with(["one", "two"]) + @script.run + end + + it "processes the files" do + MSpec.should_receive(:process) + @script.run + end + + it "exits with the exit code registered with MSpec" do + MSpec.stub(:exit_code).and_return(7) + @script.should_receive(:exit).with(7) + @script.run + end +end + +describe MSpecTag, "#register" do + before :each do + @script = MSpecTag.new + @config = @script.config + @config[:tag] = "fake:" + @config[:atags] = [] + @config[:astrings] = [] + @config[:ltags] = ["fails", "unstable"] + + @script.stub(:files).and_return([]) + @script.options "fake" + + @t = double("TagAction") + @t.stub(:register) + + @tl = double("TagListAction") + @tl.stub(:register) + end + + it "raises an ArgumentError if no recognized action is given" do + @config[:tagger] = :totally_whack + lambda { @script.register }.should raise_error(ArgumentError) + end + + describe "when config[:tagger] is the default (:add)" do + before :each do + @config[:formatter] = false + end + + it "creates a TagAction" do + TagAction.should_receive(:new).and_return(@t) + @script.register + end + + it "creates a TagAction if config[:tagger] is :del" do + @config[:tagger] = :del + @config[:outcome] = :pass + TagAction.should_receive(:new).with(:del, :pass, "fake", nil, [], []).and_return(@t) + @script.register + end + + it "calls #register on the TagAction instance" do + TagAction.should_receive(:new).and_return(@t) + @t.should_receive(:register) + @script.register + end + end + + describe "when config[:tagger] is :list" do + before :each do + TagListAction.should_receive(:new).with(@config[:ltags]).and_return(@tl) + @config[:tagger] = :list + end + + it "creates a TagListAction" do + @tl.should_receive(:register) + @script.register + end + + it "registers MSpec pretend mode" do + MSpec.should_receive(:register_mode).with(:pretend) + @script.register + end + + it "sets config[:formatter] to false" do + @script.register + @config[:formatter].should be_false + end + end + + describe "when config[:tagger] is :list_all" do + before :each do + TagListAction.should_receive(:new).with(nil).and_return(@tl) + @config[:tagger] = :list_all + end + + it "creates a TagListAction" do + @tl.should_receive(:register) + @script.register + end + + it "registers MSpec pretend mode" do + MSpec.should_receive(:register_mode).with(:pretend) + @script.register + end + + it "sets config[:formatter] to false" do + @script.register + @config[:formatter].should be_false + end + end + + describe "when config[:tagger] is :purge" do + before :each do + TagPurgeAction.should_receive(:new).and_return(@tl) + MSpec.stub(:register_mode) + @config[:tagger] = :purge + end + + it "creates a TagPurgeAction" do + @tl.should_receive(:register) + @script.register + end + + it "registers MSpec in pretend mode" do + MSpec.should_receive(:register_mode).with(:pretend) + @script.register + end + + it "registers MSpec in unguarded mode" do + MSpec.should_receive(:register_mode).with(:unguarded) + @script.register + end + + it "sets config[:formatter] to false" do + @script.register + @config[:formatter].should be_false + end + end +end diff --git a/spec/mspec/spec/expectations/expectations_spec.rb b/spec/mspec/spec/expectations/expectations_spec.rb new file mode 100644 index 0000000000..fea692f3e3 --- /dev/null +++ b/spec/mspec/spec/expectations/expectations_spec.rb @@ -0,0 +1,29 @@ +require 'spec_helper' +require 'mspec/expectations/expectations' + +describe SpecExpectationNotMetError do + it "is a subclass of StandardError" do + SpecExpectationNotMetError.ancestors.should include(StandardError) + end +end + +describe SpecExpectationNotFoundError do + it "is a subclass of StandardError" do + SpecExpectationNotFoundError.ancestors.should include(StandardError) + end +end + +describe SpecExpectationNotFoundError, "#message" do + it "returns 'No behavior expectation was found in the example'" do + m = SpecExpectationNotFoundError.new.message + m.should == "No behavior expectation was found in the example" + end +end + +describe SpecExpectation, "#fail_with" do + it "raises an SpecExpectationNotMetError" do + lambda { + SpecExpectation.fail_with "expected this", "to equal that" + }.should raise_error(SpecExpectationNotMetError, "expected this to equal that") + end +end diff --git a/spec/mspec/spec/expectations/should.rb b/spec/mspec/spec/expectations/should.rb new file mode 100644 index 0000000000..8404ff044e --- /dev/null +++ b/spec/mspec/spec/expectations/should.rb @@ -0,0 +1,72 @@ +$: << File.dirname(__FILE__) + '/../../lib' +require 'mspec' +require 'mspec/utils/script' + +# The purpose of these specs is to confirm that the #should +# and #should_not methods are functioning appropriately. We +# use a separate spec file that is invoked from the MSpec +# specs but is run by MSpec. This avoids conflicting with +# RSpec's #should and #should_not methods. + +class ShouldSpecsMonitor + def initialize + @called = 0 + end + + def expectation(state) + @called += 1 + end + + def finish + puts "I was called #{@called} times" + end +end + +# Simplistic runner +formatter = DottedFormatter.new +formatter.register + +monitor = ShouldSpecsMonitor.new +MSpec.register :expectation, monitor +MSpec.register :finish, monitor + +at_exit { MSpec.actions :finish } + +MSpec.actions :start + +# Specs +describe "MSpec expectation method #should" do + it "accepts a matcher" do + :sym.should be_kind_of(Symbol) + end + + it "causes a failue to be recorded" do + 1.should == 2 + end + + it "registers that an expectation has been encountered" do + # an empty example block causes an exception because + # no expectation was encountered + end + + it "invokes the MSpec :expectation actions" do + 1.should == 1 + end +end + +describe "MSpec expectation method #should_not" do + it "accepts a matcher" do + "sym".should_not be_kind_of(Symbol) + end + + it "causes a failure to be recorded" do + 1.should_not == 1 + end + + it "registers that an expectation has been encountered" do + end + + it "invokes the MSpec :expectation actions" do + 1.should_not == 2 + end +end diff --git a/spec/mspec/spec/expectations/should_spec.rb b/spec/mspec/spec/expectations/should_spec.rb new file mode 100644 index 0000000000..3258caf13c --- /dev/null +++ b/spec/mspec/spec/expectations/should_spec.rb @@ -0,0 +1,61 @@ +require 'spec_helper' +require 'rbconfig' + +describe "MSpec" do + before :all do + path = RbConfig::CONFIG['bindir'] + exe = RbConfig::CONFIG['ruby_install_name'] + file = File.dirname(__FILE__) + '/should.rb' + @out = `#{path}/#{exe} #{file}` + end + + describe "#should" do + it "records failures" do + @out.should include <<-EOS +1) +MSpec expectation method #should causes a failue to be recorded FAILED +Expected 1 + to equal 2 +EOS + end + + it "raises exceptions for examples with no expectations" do + @out.should include <<-EOS +2) +MSpec expectation method #should registers that an expectation has been encountered FAILED +No behavior expectation was found in the example +EOS + end + end + + describe "#should_not" do + it "records failures" do + @out.should include <<-EOS +3) +MSpec expectation method #should_not causes a failure to be recorded FAILED +Expected 1 + not to equal 1 +EOS + end + + it "raises exceptions for examples with no expectations" do + @out.should include <<-EOS +4) +MSpec expectation method #should_not registers that an expectation has been encountered FAILED +No behavior expectation was found in the example +EOS + end + end + + it "prints status information" do + @out.should include ".FF..FF." + end + + it "prints out a summary" do + @out.should include "0 files, 8 examples, 6 expectations, 4 failures, 0 errors" + end + + it "records expectations" do + @out.should include "I was called 6 times" + end +end diff --git a/spec/mspec/spec/fixtures/a_spec.rb b/spec/mspec/spec/fixtures/a_spec.rb new file mode 100644 index 0000000000..17a7e8b664 --- /dev/null +++ b/spec/mspec/spec/fixtures/a_spec.rb @@ -0,0 +1,15 @@ +unless defined?(RSpec) + describe "Foo#bar" do + it "passes" do + 1.should == 1 + end + + it "errors" do + 1.should == 2 + end + + it "fails" do + raise "failure" + end + end +end diff --git a/spec/mspec/spec/fixtures/b_spec.rb b/spec/mspec/spec/fixtures/b_spec.rb new file mode 100644 index 0000000000..f1f63317cb --- /dev/null +++ b/spec/mspec/spec/fixtures/b_spec.rb @@ -0,0 +1,7 @@ +unless defined?(RSpec) + describe "Bar#baz" do + it "works" do + 1.should == 1 + end + end +end diff --git a/spec/mspec/spec/fixtures/config.mspec b/spec/mspec/spec/fixtures/config.mspec new file mode 100644 index 0000000000..4a069e2eb0 --- /dev/null +++ b/spec/mspec/spec/fixtures/config.mspec @@ -0,0 +1,10 @@ +class MSpecScript + set :target, 'ruby' + + set :backtrace_filter, /lib\/mspec\// + + set :tags_patterns, [ + [%r(spec/fixtures/), 'spec/fixtures/tags/'], + [/_spec.rb$/, '_tags.txt'] + ] +end diff --git a/spec/mspec/spec/fixtures/my_ruby b/spec/mspec/spec/fixtures/my_ruby new file mode 100755 index 0000000000..4d552f27fb --- /dev/null +++ b/spec/mspec/spec/fixtures/my_ruby @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +echo $RUBY_EXE +ruby "$@" diff --git a/spec/mspec/spec/fixtures/print_interpreter_spec.rb b/spec/mspec/spec/fixtures/print_interpreter_spec.rb new file mode 100644 index 0000000000..e1c514dc87 --- /dev/null +++ b/spec/mspec/spec/fixtures/print_interpreter_spec.rb @@ -0,0 +1,4 @@ +unless defined?(RSpec) + puts ENV["RUBY_EXE"] + puts ruby_cmd("nil").split.first +end diff --git a/spec/mspec/spec/fixtures/tagging_spec.rb b/spec/mspec/spec/fixtures/tagging_spec.rb new file mode 100644 index 0000000000..0097fd1808 --- /dev/null +++ b/spec/mspec/spec/fixtures/tagging_spec.rb @@ -0,0 +1,16 @@ +# encoding: utf-8 +unless defined?(RSpec) + describe "Tag#me" do + it "passes" do + 1.should == 1 + end + + it "errors" do + 1.should == 2 + end + + it "érròrs in unicode" do + 1.should == 2 + end + end +end diff --git a/spec/mspec/spec/guards/block_device_spec.rb b/spec/mspec/spec/guards/block_device_spec.rb new file mode 100644 index 0000000000..3b437b6d74 --- /dev/null +++ b/spec/mspec/spec/guards/block_device_spec.rb @@ -0,0 +1,46 @@ +require 'spec_helper' +require 'mspec/guards' + +describe Object, "#with_block_device" do + before :each do + ScratchPad.clear + + @guard = BlockDeviceGuard.new + BlockDeviceGuard.stub(:new).and_return(@guard) + end + + platform_is_not :freebsd, :windows do + it "yields if block device is available" do + @guard.should_receive(:`).and_return("block devices") + with_block_device { ScratchPad.record :yield } + ScratchPad.recorded.should == :yield + end + + it "does not yield if block device is not available" do + @guard.should_receive(:`).and_return(nil) + with_block_device { ScratchPad.record :yield } + ScratchPad.recorded.should_not == :yield + end + end + + platform_is :freebsd, :windows do + it "does not yield, since platform does not support block devices" do + @guard.should_not_receive(:`) + with_block_device { ScratchPad.record :yield } + ScratchPad.recorded.should_not == :yield + end + end + + it "sets the name of the guard to :with_block_device" do + with_block_device { } + @guard.name.should == :with_block_device + end + + it "calls #unregister even when an exception is raised in the guard block" do + @guard.should_receive(:match?).and_return(true) + @guard.should_receive(:unregister) + lambda do + with_block_device { raise Exception } + end.should raise_error(Exception) + end +end diff --git a/spec/mspec/spec/guards/bug_spec.rb b/spec/mspec/spec/guards/bug_spec.rb new file mode 100644 index 0000000000..93c549041a --- /dev/null +++ b/spec/mspec/spec/guards/bug_spec.rb @@ -0,0 +1,151 @@ +require 'spec_helper' +require 'mspec/guards' + +describe BugGuard, "#match? when #implementation? is 'ruby'" do + before :all do + @verbose = $VERBOSE + $VERBOSE = nil + end + + after :all do + $VERBOSE = @verbose + end + + before :each do + hide_deprecation_warnings + stub_const "VersionGuard::FULL_RUBY_VERSION", SpecVersion.new('1.8.6') + @ruby_name = Object.const_get :RUBY_NAME + Object.const_set :RUBY_NAME, 'ruby' + end + + after :each do + Object.const_set :RUBY_NAME, @ruby_name + end + + it "returns false when version argument is less than RUBY_VERSION" do + BugGuard.new("#1", "1.8.5").match?.should == false + end + + it "returns true when version argument is equal to RUBY_VERSION" do + BugGuard.new("#1", "1.8.6").match?.should == true + end + + it "returns true when version argument is greater than RUBY_VERSION" do + BugGuard.new("#1", "1.8.7").match?.should == true + end + + it "returns true when version argument implicitly includes RUBY_VERSION" do + BugGuard.new("#1", "1.8").match?.should == true + BugGuard.new("#1", "1.8.6").match?.should == true + end + + it "returns true when the argument range includes RUBY_VERSION" do + BugGuard.new("#1", '1.8.5'..'1.8.7').match?.should == true + BugGuard.new("#1", '1.8'..'1.9').match?.should == true + BugGuard.new("#1", '1.8'...'1.9').match?.should == true + BugGuard.new("#1", '1.8'..'1.8.6').match?.should == true + BugGuard.new("#1", '1.8.5'..'1.8.6').match?.should == true + BugGuard.new("#1", ''...'1.8.7').match?.should == true + end + + it "returns false when the argument range does not include RUBY_VERSION" do + BugGuard.new("#1", '1.8.7'..'1.8.9').match?.should == false + BugGuard.new("#1", '1.8.4'..'1.8.5').match?.should == false + BugGuard.new("#1", '1.8.4'...'1.8.6').match?.should == false + BugGuard.new("#1", '1.8.5'...'1.8.6').match?.should == false + BugGuard.new("#1", ''...'1.8.6').match?.should == false + end + + it "returns false when MSpec.mode?(:no_ruby_bug) is true" do + MSpec.should_receive(:mode?).with(:no_ruby_bug).twice.and_return(:true) + BugGuard.new("#1", "1.8.5").match?.should == false + BugGuard.new("#1", "1.8").match?.should == false + end +end + +describe BugGuard, "#match? when #implementation? is not 'ruby'" do + before :all do + @verbose = $VERBOSE + $VERBOSE = nil + end + + after :all do + $VERBOSE = @verbose + end + + before :each do + hide_deprecation_warnings + @ruby_version = Object.const_get :RUBY_VERSION + @ruby_name = Object.const_get :RUBY_NAME + + Object.const_set :RUBY_VERSION, '1.8.6' + Object.const_set :RUBY_NAME, 'jruby' + end + + after :each do + Object.const_set :RUBY_VERSION, @ruby_version + Object.const_set :RUBY_NAME, @ruby_name + end + + it "returns false when version argument is less than RUBY_VERSION" do + BugGuard.new("#1", "1.8").match?.should == false + BugGuard.new("#1", "1.8.6").match?.should == false + end + + it "returns false when version argument is equal to RUBY_VERSION" do + BugGuard.new("#1", "1.8.6").match?.should == false + end + + it "returns false when version argument is greater than RUBY_VERSION" do + BugGuard.new("#1", "1.8.7").match?.should == false + end + + it "returns false no matter if the argument range includes RUBY_VERSION" do + BugGuard.new("#1", '1.8'...'1.9').match?.should == false + BugGuard.new("#1", '1.8.5'...'1.8.7').match?.should == false + BugGuard.new("#1", '1.8.4'...'1.8.6').match?.should == false + end + + it "returns false when MSpec.mode?(:no_ruby_bug) is true" do + MSpec.stub(:mode?).and_return(:true) + BugGuard.new("#1", "1.8.6").match?.should == false + end +end + +describe Object, "#ruby_bug" do + before :each do + hide_deprecation_warnings + @guard = BugGuard.new "#1234", "x.x.x" + BugGuard.stub(:new).and_return(@guard) + ScratchPad.clear + end + + it "yields when #match? returns false" do + @guard.stub(:match?).and_return(false) + ruby_bug("#1234", "1.8.6") { ScratchPad.record :yield } + ScratchPad.recorded.should == :yield + end + + it "does not yield when #match? returns true" do + @guard.stub(:match?).and_return(true) + ruby_bug("#1234", "1.8.6") { ScratchPad.record :yield } + ScratchPad.recorded.should_not == :yield + end + + it "requires a bug tracker number and a version number" do + lambda { ruby_bug { } }.should raise_error(ArgumentError) + lambda { ruby_bug("#1234") { } }.should raise_error(ArgumentError) + end + + it "sets the name of the guard to :ruby_bug" do + ruby_bug("#1234", "1.8.6") { } + @guard.name.should == :ruby_bug + end + + it "calls #unregister even when an exception is raised in the guard block" do + @guard.should_receive(:unregister) + lambda do + ruby_bug("", "") { raise Exception } + end.should raise_error(Exception) + end +end diff --git a/spec/mspec/spec/guards/conflict_spec.rb b/spec/mspec/spec/guards/conflict_spec.rb new file mode 100644 index 0000000000..e06a2809ee --- /dev/null +++ b/spec/mspec/spec/guards/conflict_spec.rb @@ -0,0 +1,51 @@ +require 'spec_helper' +require 'mspec/guards' + +describe Object, "#conflicts_with" do + before :each do + ScratchPad.clear + end + + it "does not yield if Object.constants includes any of the arguments" do + Object.stub(:constants).and_return(["SomeClass", "OtherClass"]) + conflicts_with(:SomeClass, :AClass, :BClass) { ScratchPad.record :yield } + ScratchPad.recorded.should_not == :yield + end + + it "does not yield if Object.constants (as Symbols) includes any of the arguments" do + Object.stub(:constants).and_return([:SomeClass, :OtherClass]) + conflicts_with(:SomeClass, :AClass, :BClass) { ScratchPad.record :yield } + ScratchPad.recorded.should_not == :yield + end + + it "yields if Object.constants does not include any of the arguments" do + Object.stub(:constants).and_return(["SomeClass", "OtherClass"]) + conflicts_with(:AClass, :BClass) { ScratchPad.record :yield } + ScratchPad.recorded.should == :yield + end + + it "yields if Object.constants (as Symbols) does not include any of the arguments" do + Object.stub(:constants).and_return([:SomeClass, :OtherClass]) + conflicts_with(:AClass, :BClass) { ScratchPad.record :yield } + ScratchPad.recorded.should == :yield + end +end + +describe Object, "#conflicts_with" do + before :each do + @guard = ConflictsGuard.new + ConflictsGuard.stub(:new).and_return(@guard) + end + + it "sets the name of the guard to :conflicts_with" do + conflicts_with(:AClass, :BClass) { } + @guard.name.should == :conflicts_with + end + + it "calls #unregister even when an exception is raised in the guard block" do + @guard.should_receive(:unregister) + lambda do + conflicts_with(:AClass, :BClass) { raise Exception } + end.should raise_error(Exception) + end +end diff --git a/spec/mspec/spec/guards/endian_spec.rb b/spec/mspec/spec/guards/endian_spec.rb new file mode 100644 index 0000000000..5b40c203ab --- /dev/null +++ b/spec/mspec/spec/guards/endian_spec.rb @@ -0,0 +1,55 @@ +require 'spec_helper' +require 'mspec/guards' + +describe Object, "#big_endian" do + before :each do + @guard = BigEndianGuard.new + BigEndianGuard.stub(:new).and_return(@guard) + ScratchPad.clear + end + + it "yields on big-endian platforms" do + @guard.stub(:pattern).and_return([?\001]) + big_endian { ScratchPad.record :yield } + ScratchPad.recorded.should == :yield + end + + it "does not yield on little-endian platforms" do + @guard.stub(:pattern).and_return([?\000]) + big_endian { ScratchPad.record :yield } + ScratchPad.recorded.should_not == :yield + end + + it "sets the name of the guard to :big_endian" do + big_endian { } + @guard.name.should == :big_endian + end + + it "calls #unregister even when an exception is raised in the guard block" do + @guard.stub(:pattern).and_return([?\001]) + @guard.should_receive(:unregister) + lambda do + big_endian { raise Exception } + end.should raise_error(Exception) + end +end + +describe Object, "#little_endian" do + before :each do + @guard = BigEndianGuard.new + BigEndianGuard.stub(:new).and_return(@guard) + ScratchPad.clear + end + + it "yields on little-endian platforms" do + @guard.stub(:pattern).and_return([?\000]) + little_endian { ScratchPad.record :yield } + ScratchPad.recorded.should == :yield + end + + it "does not yield on big-endian platforms" do + @guard.stub(:pattern).and_return([?\001]) + little_endian { ScratchPad.record :yield } + ScratchPad.recorded.should_not == :yield + end +end diff --git a/spec/mspec/spec/guards/feature_spec.rb b/spec/mspec/spec/guards/feature_spec.rb new file mode 100644 index 0000000000..d14e5f8e67 --- /dev/null +++ b/spec/mspec/spec/guards/feature_spec.rb @@ -0,0 +1,80 @@ +require 'spec_helper' +require 'mspec/guards' + +describe FeatureGuard, ".enabled?" do + it "returns true if the feature is enabled" do + MSpec.should_receive(:feature_enabled?).with(:encoding).and_return(true) + FeatureGuard.enabled?(:encoding).should be_true + end + + it "returns false if the feature is not enabled" do + MSpec.should_receive(:feature_enabled?).with(:encoding).and_return(false) + FeatureGuard.enabled?(:encoding).should be_false + end + + it "returns true if all the features are enabled" do + MSpec.should_receive(:feature_enabled?).with(:one).and_return(true) + MSpec.should_receive(:feature_enabled?).with(:two).and_return(true) + FeatureGuard.enabled?(:one, :two).should be_true + end + + it "returns false if any of the features are not enabled" do + MSpec.should_receive(:feature_enabled?).with(:one).and_return(true) + MSpec.should_receive(:feature_enabled?).with(:two).and_return(false) + FeatureGuard.enabled?(:one, :two).should be_false + end +end + +describe Object, "#with_feature" do + before :each do + ScratchPad.clear + + @guard = FeatureGuard.new :encoding + FeatureGuard.stub(:new).and_return(@guard) + end + + it "sets the name of the guard to :with_feature" do + with_feature(:encoding) { } + @guard.name.should == :with_feature + end + + it "calls #unregister even when an exception is raised in the guard block" do + @guard.should_receive(:match?).and_return(true) + @guard.should_receive(:unregister) + lambda do + with_feature { raise Exception } + end.should raise_error(Exception) + end +end + +describe Object, "#with_feature" do + before :each do + ScratchPad.clear + end + + it "yields if the feature is enabled" do + MSpec.should_receive(:feature_enabled?).with(:encoding).and_return(true) + with_feature(:encoding) { ScratchPad.record :yield } + ScratchPad.recorded.should == :yield + end + + it "yields if all the features are enabled" do + MSpec.should_receive(:feature_enabled?).with(:one).and_return(true) + MSpec.should_receive(:feature_enabled?).with(:two).and_return(true) + with_feature(:one, :two) { ScratchPad.record :yield } + ScratchPad.recorded.should == :yield + end + + it "does not yield if the feature is not enabled" do + MSpec.should_receive(:feature_enabled?).with(:encoding).and_return(false) + with_feature(:encoding) { ScratchPad.record :yield } + ScratchPad.recorded.should be_nil + end + + it "does not yield if any of the features are not enabled" do + MSpec.should_receive(:feature_enabled?).with(:one).and_return(true) + MSpec.should_receive(:feature_enabled?).with(:two).and_return(false) + with_feature(:one, :two) { ScratchPad.record :yield } + ScratchPad.recorded.should be_nil + end +end diff --git a/spec/mspec/spec/guards/guard_spec.rb b/spec/mspec/spec/guards/guard_spec.rb new file mode 100644 index 0000000000..ca7dba6455 --- /dev/null +++ b/spec/mspec/spec/guards/guard_spec.rb @@ -0,0 +1,180 @@ +require 'spec_helper' +require 'mspec/guards' +require 'rbconfig' + +describe SpecGuard, ".ruby_version" do + before :each do + @ruby_version = Object.const_get :RUBY_VERSION + Object.const_set :RUBY_VERSION, "8.2.3" + end + + after :each do + Object.const_set :RUBY_VERSION, @ruby_version + end + + it "returns the full version for :full" do + SpecGuard.ruby_version(:full).should == "8.2.3" + end + + it "returns major.minor.tiny for :tiny" do + SpecGuard.ruby_version(:tiny).should == "8.2.3" + end + + it "returns major.minor.tiny for :teeny" do + SpecGuard.ruby_version(:tiny).should == "8.2.3" + end + + it "returns major.minor for :minor" do + SpecGuard.ruby_version(:minor).should == "8.2" + end + + it "defaults to :minor" do + SpecGuard.ruby_version.should == "8.2" + end + + it "returns major for :major" do + SpecGuard.ruby_version(:major).should == "8" + end +end + +describe SpecGuard, "#yield?" do + before :each do + MSpec.clear_modes + @guard = SpecGuard.new + @guard.stub(:match?).and_return(false) + end + + after :each do + MSpec.unregister :add, @guard + MSpec.clear_modes + SpecGuard.clear_guards + end + + it "returns true if MSpec.mode?(:unguarded) is true" do + MSpec.register_mode :unguarded + @guard.yield?.should == true + end + + it "returns true if MSpec.mode?(:verify) is true" do + MSpec.register_mode :verify + @guard.yield?.should == true + end + + it "returns true if MSpec.mode?(:verify) is true regardless of invert being true" do + MSpec.register_mode :verify + @guard.yield?(true).should == true + end + + it "returns true if MSpec.mode?(:report) is true" do + MSpec.register_mode :report + @guard.yield?.should == true + end + + it "returns true if MSpec.mode?(:report) is true regardless of invert being true" do + MSpec.register_mode :report + @guard.yield?(true).should == true + end + + it "returns true if MSpec.mode?(:report_on) is true and SpecGuards.guards contains the named guard" do + MSpec.register_mode :report_on + SpecGuard.guards << :guard_name + @guard.yield?.should == false + @guard.name = :guard_name + @guard.yield?.should == true + end + + it "returns #match? if neither report nor verify mode are true" do + @guard.stub(:match?).and_return(false) + @guard.yield?.should == false + @guard.stub(:match?).and_return(true) + @guard.yield?.should == true + end + + it "returns #match? if invert is true and neither report nor verify mode are true" do + @guard.stub(:match?).and_return(false) + @guard.yield?(true).should == true + @guard.stub(:match?).and_return(true) + @guard.yield?(true).should == false + end +end + +describe SpecGuard, "#match?" do + before :each do + @guard = SpecGuard.new + end + + it "must be implemented in subclasses" do + lambda { + @guard.match? + }.should raise_error("must be implemented by the subclass") + end +end + +describe SpecGuard, "#unregister" do + before :each do + MSpec.stub(:unregister) + @guard = SpecGuard.new + end + + it "unregisters from MSpec :add actions" do + MSpec.should_receive(:unregister).with(:add, @guard) + @guard.unregister + end +end + +describe SpecGuard, "#record" do + after :each do + SpecGuard.clear + end + + it "saves the name of the guarded spec under the name of the guard" do + guard = SpecGuard.new "a", "1.8"..."1.9" + guard.name = :named_guard + guard.record "SomeClass#action returns true" + SpecGuard.report.should == { + 'named_guard a, 1.8...1.9' => ["SomeClass#action returns true"] + } + end +end + +describe SpecGuard, ".guards" do + it "returns an Array" do + SpecGuard.guards.should be_kind_of(Array) + end +end + +describe SpecGuard, ".clear_guards" do + it "resets the array to empty" do + SpecGuard.guards << :guard + SpecGuard.guards.should == [:guard] + SpecGuard.clear_guards + SpecGuard.guards.should == [] + end +end + +describe SpecGuard, ".finish" do + before :each do + $stdout = @out = IOStub.new + end + + after :each do + $stdout = STDOUT + SpecGuard.clear + end + + it "prints the descriptions of the guarded specs" do + guard = SpecGuard.new "a", "1.8"..."1.9" + guard.name = :named_guard + guard.record "SomeClass#action returns true" + guard.record "SomeClass#reverse returns false" + SpecGuard.finish + $stdout.should == %[ + +2 specs omitted by guard: named_guard a, 1.8...1.9: + +SomeClass#action returns true +SomeClass#reverse returns false + +] + end +end diff --git a/spec/mspec/spec/guards/platform_spec.rb b/spec/mspec/spec/guards/platform_spec.rb new file mode 100644 index 0000000000..578773e476 --- /dev/null +++ b/spec/mspec/spec/guards/platform_spec.rb @@ -0,0 +1,331 @@ +require 'spec_helper' +require 'mspec/guards' + +describe Object, "#platform_is" do + before :each do + @guard = PlatformGuard.new :dummy + PlatformGuard.stub(:new).and_return(@guard) + ScratchPad.clear + end + + it "does not yield when #os? returns false" do + PlatformGuard.stub(:os?).and_return(false) + platform_is(:ruby) { ScratchPad.record :yield } + ScratchPad.recorded.should_not == :yield + end + + it "yields when #os? returns true" do + PlatformGuard.stub(:os?).and_return(true) + platform_is(:solarce) { ScratchPad.record :yield } + ScratchPad.recorded.should == :yield + end + + it "sets the name of the guard to :platform_is" do + platform_is(:solarce) { } + @guard.name.should == :platform_is + end + + it "calls #unregister even when an exception is raised in the guard block" do + @guard.should_receive(:match?).and_return(true) + @guard.should_receive(:unregister) + lambda do + platform_is(:solarce) { raise Exception } + end.should raise_error(Exception) + end +end + +describe Object, "#platform_is_not" do + before :each do + @guard = PlatformGuard.new :dummy + PlatformGuard.stub(:new).and_return(@guard) + ScratchPad.clear + end + + it "does not yield when #os? returns true" do + PlatformGuard.stub(:os?).and_return(true) + platform_is_not(:ruby) { ScratchPad.record :yield } + ScratchPad.recorded.should_not == :yield + end + + it "yields when #os? returns false" do + PlatformGuard.stub(:os?).and_return(false) + platform_is_not(:solarce) { ScratchPad.record :yield } + ScratchPad.recorded.should == :yield + end + + it "sets the name of the guard to :platform_is_not" do + platform_is_not(:solarce) { } + @guard.name.should == :platform_is_not + end + + it "calls #unregister even when an exception is raised in the guard block" do + @guard.should_receive(:match?).and_return(false) + @guard.should_receive(:unregister) + lambda do + platform_is_not(:solarce) { raise Exception } + end.should raise_error(Exception) + end +end + +describe Object, "#platform_is :wordsize => SIZE_SPEC" do + before :each do + @guard = PlatformGuard.new :darwin, :wordsize => 32 + PlatformGuard.stub(:os?).and_return(true) + PlatformGuard.stub(:new).and_return(@guard) + ScratchPad.clear + end + + it "yields when #wordsize? returns true" do + PlatformGuard.stub(:wordsize?).and_return(true) + platform_is(:wordsize => 32) { ScratchPad.record :yield } + ScratchPad.recorded.should == :yield + end + + it "doesn not yield when #wordsize? returns false" do + PlatformGuard.stub(:wordsize?).and_return(false) + platform_is(:wordsize => 32) { ScratchPad.record :yield } + ScratchPad.recorded.should_not == :yield + end +end + +describe Object, "#platform_is_not :wordsize => SIZE_SPEC" do + before :each do + @guard = PlatformGuard.new :darwin, :wordsize => 32 + PlatformGuard.stub(:os?).and_return(true) + PlatformGuard.stub(:new).and_return(@guard) + ScratchPad.clear + end + + it "yields when #wordsize? returns false" do + PlatformGuard.stub(:wordsize?).and_return(false) + platform_is_not(:wordsize => 32) { ScratchPad.record :yield } + ScratchPad.recorded.should == :yield + end + + it "doesn not yield when #wordsize? returns true" do + PlatformGuard.stub(:wordsize?).and_return(true) + platform_is_not(:wordsize => 32) { ScratchPad.record :yield } + ScratchPad.recorded.should_not == :yield + end +end + +describe PlatformGuard, ".implementation?" do + before :all do + @verbose = $VERBOSE + $VERBOSE = nil + end + + after :all do + $VERBOSE = @verbose + end + + before :each do + @ruby_name = Object.const_get :RUBY_NAME + end + + after :each do + Object.const_set :RUBY_NAME, @ruby_name + end + + it "returns true if passed :ruby and RUBY_NAME == 'ruby'" do + Object.const_set :RUBY_NAME, 'ruby' + PlatformGuard.implementation?(:ruby).should == true + end + + it "returns true if passed :rubinius and RUBY_NAME == 'rbx'" do + Object.const_set :RUBY_NAME, 'rbx' + PlatformGuard.implementation?(:rubinius).should == true + end + + it "returns true if passed :jruby and RUBY_NAME == 'jruby'" do + Object.const_set :RUBY_NAME, 'jruby' + PlatformGuard.implementation?(:jruby).should == true + end + + it "returns true if passed :ironruby and RUBY_NAME == 'ironruby'" do + Object.const_set :RUBY_NAME, 'ironruby' + PlatformGuard.implementation?(:ironruby).should == true + end + + it "returns true if passed :maglev and RUBY_NAME == 'maglev'" do + Object.const_set :RUBY_NAME, 'maglev' + PlatformGuard.implementation?(:maglev).should == true + end + + it "returns true if passed :topaz and RUBY_NAME == 'topaz'" do + Object.const_set :RUBY_NAME, 'topaz' + PlatformGuard.implementation?(:topaz).should == true + end + + it "returns true if passed :ruby and RUBY_NAME matches /^ruby/" do + Object.const_set :RUBY_NAME, 'ruby' + PlatformGuard.implementation?(:ruby).should == true + + Object.const_set :RUBY_NAME, 'ruby1.8' + PlatformGuard.implementation?(:ruby).should == true + + Object.const_set :RUBY_NAME, 'ruby1.9' + PlatformGuard.implementation?(:ruby).should == true + end + + it "raises an error when passed an unrecognized name" do + Object.const_set :RUBY_NAME, 'ruby' + lambda { + PlatformGuard.implementation?(:python) + }.should raise_error(/unknown implementation/) + end +end + +describe PlatformGuard, ".standard?" do + it "returns true if implementation? returns true" do + PlatformGuard.should_receive(:implementation?).with(:ruby).and_return(true) + PlatformGuard.standard?.should be_true + end + + it "returns false if implementation? returns false" do + PlatformGuard.should_receive(:implementation?).with(:ruby).and_return(false) + PlatformGuard.standard?.should be_false + end +end + +describe PlatformGuard, ".wordsize?" do + it "returns true when arg is 32 and 1.size is 4" do + PlatformGuard.wordsize?(32).should == (1.size == 4) + end + + it "returns true when arg is 64 and 1.size is 8" do + PlatformGuard.wordsize?(64).should == (1.size == 8) + end +end + +describe PlatformGuard, ".os?" do + before :each do + stub_const 'PlatformGuard::HOST_OS', 'solarce' + end + + it "returns false when arg does not match the platform" do + PlatformGuard.os?(:ruby).should == false + end + + it "returns false when no arg matches the platform" do + PlatformGuard.os?(:ruby, :jruby, :rubinius, :maglev).should == false + end + + it "returns true when arg matches the platform" do + PlatformGuard.os?(:solarce).should == true + end + + it "returns true when any arg matches the platform" do + PlatformGuard.os?(:ruby, :jruby, :solarce, :rubinius, :maglev).should == true + end + + it "returns true when arg is :windows and the platform contains 'mswin'" do + stub_const 'PlatformGuard::HOST_OS', 'mswin32' + PlatformGuard.os?(:windows).should == true + end + + it "returns true when arg is :windows and the platform contains 'mingw'" do + stub_const 'PlatformGuard::HOST_OS', 'i386-mingw32' + PlatformGuard.os?(:windows).should == true + end + + it "returns false when arg is not :windows and RbConfig::CONFIG['host_os'] contains 'mswin'" do + stub_const 'PlatformGuard::HOST_OS', 'i386-mswin32' + PlatformGuard.os?(:linux).should == false + end + + it "returns false when arg is not :windows and RbConfig::CONFIG['host_os'] contains 'mingw'" do + stub_const 'PlatformGuard::HOST_OS', 'i386-mingw32' + PlatformGuard.os?(:linux).should == false + end +end + +describe PlatformGuard, ".os? on JRuby" do + before :all do + @verbose = $VERBOSE + $VERBOSE = nil + end + + after :all do + $VERBOSE = @verbose + end + + before :each do + @ruby_platform = Object.const_get :RUBY_PLATFORM + Object.const_set :RUBY_PLATFORM, 'java' + end + + after :each do + Object.const_set :RUBY_PLATFORM, @ruby_platform + end + + it "raises an error when testing for a :java platform" do + lambda { + PlatformGuard.os?(:java) + }.should raise_error(":java is not a valid OS") + end + + it "returns true when arg is :windows and RUBY_PLATFORM contains 'java' and os?(:windows) is true" do + stub_const 'PlatformGuard::HOST_OS', 'mswin32' + PlatformGuard.os?(:windows).should == true + end + + it "returns true when RUBY_PLATFORM contains 'java' and os?(argument) is true" do + stub_const 'PlatformGuard::HOST_OS', 'amiga' + PlatformGuard.os?(:amiga).should == true + end +end + +describe PlatformGuard, ".os?" do + before :each do + stub_const 'PlatformGuard::HOST_OS', 'unreal' + end + + it "returns true if argument matches RbConfig::CONFIG['host_os']" do + PlatformGuard.os?(:unreal).should == true + end + + it "returns true if any argument matches RbConfig::CONFIG['host_os']" do + PlatformGuard.os?(:bsd, :unreal, :amiga).should == true + end + + it "returns false if no argument matches RbConfig::CONFIG['host_os']" do + PlatformGuard.os?(:bsd, :netbsd, :amiga, :msdos).should == false + end + + it "returns false if argument does not match RbConfig::CONFIG['host_os']" do + PlatformGuard.os?(:amiga).should == false + end + + it "returns true when arg is :windows and RbConfig::CONFIG['host_os'] contains 'mswin'" do + stub_const 'PlatformGuard::HOST_OS', 'i386-mswin32' + PlatformGuard.os?(:windows).should == true + end + + it "returns true when arg is :windows and RbConfig::CONFIG['host_os'] contains 'mingw'" do + stub_const 'PlatformGuard::HOST_OS', 'i386-mingw32' + PlatformGuard.os?(:windows).should == true + end + + it "returns false when arg is not :windows and RbConfig::CONFIG['host_os'] contains 'mswin'" do + stub_const 'PlatformGuard::HOST_OS', 'i386-mingw32' + PlatformGuard.os?(:linux).should == false + end + + it "returns false when arg is not :windows and RbConfig::CONFIG['host_os'] contains 'mingw'" do + stub_const 'PlatformGuard::HOST_OS', 'i386-mingw32' + PlatformGuard.os?(:linux).should == false + end +end + +describe PlatformGuard, ".windows?" do + it "returns true on windows" do + stub_const 'PlatformGuard::HOST_OS', 'i386-mingw32' + PlatformGuard.windows?.should == true + end + + it "returns false on non-windows" do + stub_const 'PlatformGuard::HOST_OS', 'i586-linux' + PlatformGuard.windows?.should == false + end +end diff --git a/spec/mspec/spec/guards/quarantine_spec.rb b/spec/mspec/spec/guards/quarantine_spec.rb new file mode 100644 index 0000000000..e5c7da7939 --- /dev/null +++ b/spec/mspec/spec/guards/quarantine_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper' +require 'mspec/guards' + +describe QuarantineGuard, "#match?" do + it "returns true" do + QuarantineGuard.new.match?.should == true + end +end + +describe Object, "#quarantine!" do + before :each do + ScratchPad.clear + + @guard = QuarantineGuard.new + QuarantineGuard.stub(:new).and_return(@guard) + end + + it "does not yield" do + quarantine! { ScratchPad.record :yield } + ScratchPad.recorded.should_not == :yield + end + + it "sets the name of the guard to :quarantine!" do + quarantine! { } + @guard.name.should == :quarantine! + end + + it "calls #unregister even when an exception is raised in the guard block" do + @guard.should_receive(:match?).and_return(false) + @guard.should_receive(:unregister) + lambda do + quarantine! { raise Exception } + end.should raise_error(Exception) + end +end diff --git a/spec/mspec/spec/guards/superuser_spec.rb b/spec/mspec/spec/guards/superuser_spec.rb new file mode 100644 index 0000000000..f8815057e1 --- /dev/null +++ b/spec/mspec/spec/guards/superuser_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper' +require 'mspec/guards' + +describe Object, "#as_superuser" do + before :each do + @guard = SuperUserGuard.new + SuperUserGuard.stub(:new).and_return(@guard) + ScratchPad.clear + end + + it "does not yield when Process.euid is not 0" do + Process.stub(:euid).and_return(501) + as_superuser { ScratchPad.record :yield } + ScratchPad.recorded.should_not == :yield + end + + it "yields when Process.euid is 0" do + Process.stub(:euid).and_return(0) + as_superuser { ScratchPad.record :yield } + ScratchPad.recorded.should == :yield + end + + it "sets the name of the guard to :as_superuser" do + as_superuser { } + @guard.name.should == :as_superuser + end + + it "calls #unregister even when an exception is raised in the guard block" do + @guard.should_receive(:match?).and_return(true) + @guard.should_receive(:unregister) + lambda do + as_superuser { raise Exception } + end.should raise_error(Exception) + end +end diff --git a/spec/mspec/spec/guards/support_spec.rb b/spec/mspec/spec/guards/support_spec.rb new file mode 100644 index 0000000000..43a7e76f06 --- /dev/null +++ b/spec/mspec/spec/guards/support_spec.rb @@ -0,0 +1,69 @@ +require 'spec_helper' +require 'mspec/guards' + +describe Object, "#not_supported_on" do + before :all do + @verbose = $VERBOSE + $VERBOSE = nil + @ruby_name = Object.const_get :RUBY_NAME if Object.const_defined? :RUBY_NAME + end + + after :all do + $VERBOSE = @verbose + if @ruby_name + Object.const_set :RUBY_NAME, @ruby_name + else + Object.send :remove_const, :RUBY_NAME + end + end + + before :each do + ScratchPad.clear + end + + it "raises an Exception when passed :ruby" do + Object.const_set :RUBY_NAME, "jruby" + lambda { + not_supported_on(:ruby) { ScratchPad.record :yield } + }.should raise_error(Exception) + ScratchPad.recorded.should_not == :yield + end + + it "does not yield when #implementation? returns true" do + Object.const_set :RUBY_NAME, "jruby" + not_supported_on(:jruby) { ScratchPad.record :yield } + ScratchPad.recorded.should_not == :yield + end + + it "yields when #standard? returns true" do + Object.const_set :RUBY_NAME, "ruby" + not_supported_on(:rubinius) { ScratchPad.record :yield } + ScratchPad.recorded.should == :yield + end + + it "yields when #implementation? returns false" do + Object.const_set :RUBY_NAME, "jruby" + not_supported_on(:rubinius) { ScratchPad.record :yield } + ScratchPad.recorded.should == :yield + end +end + +describe Object, "#not_supported_on" do + before :each do + @guard = SupportedGuard.new + SupportedGuard.stub(:new).and_return(@guard) + end + + it "sets the name of the guard to :not_supported_on" do + not_supported_on(:rubinius) { } + @guard.name.should == :not_supported_on + end + + it "calls #unregister even when an exception is raised in the guard block" do + @guard.should_receive(:match?).and_return(false) + @guard.should_receive(:unregister) + lambda do + not_supported_on(:rubinius) { raise Exception } + end.should raise_error(Exception) + end +end diff --git a/spec/mspec/spec/guards/user_spec.rb b/spec/mspec/spec/guards/user_spec.rb new file mode 100644 index 0000000000..2de4db7390 --- /dev/null +++ b/spec/mspec/spec/guards/user_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' +require 'mspec/guards' + +describe Object, "#as_user" do + before :each do + ScratchPad.clear + end + + it "yields when the Process.euid is not 0" do + Process.stub(:euid).and_return(501) + as_user { ScratchPad.record :yield } + ScratchPad.recorded.should == :yield + end + + it "does not yield when the Process.euid is 0" do + Process.stub(:euid).and_return(0) + as_user { ScratchPad.record :yield } + ScratchPad.recorded.should_not == :yield + end +end diff --git a/spec/mspec/spec/guards/version_spec.rb b/spec/mspec/spec/guards/version_spec.rb new file mode 100644 index 0000000000..f11e3dbd94 --- /dev/null +++ b/spec/mspec/spec/guards/version_spec.rb @@ -0,0 +1,83 @@ +require 'spec_helper' +require 'mspec/guards' + +# The VersionGuard specifies a version of Ruby with a String of +# the form: v = 'major.minor.tiny'. +# +# A VersionGuard instance can be created with a single String, +# which means any version >= each component of v. +# Or, the guard can be created with a Range, a..b, or a...b, +# where a, b are of the same form as v. The meaning of the Range +# is as typically understood: a..b means v >= a and v <= b; +# a...b means v >= a and v < b. + +describe VersionGuard, "#match?" do + before :each do + hide_deprecation_warnings + stub_const "VersionGuard::FULL_RUBY_VERSION", SpecVersion.new('1.8.6') + end + + it "returns true when the argument is equal to RUBY_VERSION" do + VersionGuard.new('1.8.6').match?.should == true + end + + it "returns true when the argument is less than RUBY_VERSION" do + VersionGuard.new('1.8').match?.should == true + VersionGuard.new('1.8.5').match?.should == true + end + + it "returns false when the argument is greater than RUBY_VERSION" do + VersionGuard.new('1.8.7').match?.should == false + VersionGuard.new('1.9.2').match?.should == false + end + + it "returns true when the argument range includes RUBY_VERSION" do + VersionGuard.new('1.8.5'..'1.8.7').match?.should == true + VersionGuard.new('1.8'..'1.9').match?.should == true + VersionGuard.new('1.8'...'1.9').match?.should == true + VersionGuard.new('1.8'..'1.8.6').match?.should == true + VersionGuard.new('1.8.5'..'1.8.6').match?.should == true + VersionGuard.new(''...'1.8.7').match?.should == true + end + + it "returns false when the argument range does not include RUBY_VERSION" do + VersionGuard.new('1.8.7'..'1.8.9').match?.should == false + VersionGuard.new('1.8.4'..'1.8.5').match?.should == false + VersionGuard.new('1.8.4'...'1.8.6').match?.should == false + VersionGuard.new('1.8.5'...'1.8.6').match?.should == false + VersionGuard.new(''...'1.8.6').match?.should == false + end +end + +describe Object, "#ruby_version_is" do + before :each do + @guard = VersionGuard.new 'x.x.x' + VersionGuard.stub(:new).and_return(@guard) + ScratchPad.clear + end + + it "yields when #match? returns true" do + @guard.stub(:match?).and_return(true) + ruby_version_is('x.x.x') { ScratchPad.record :yield } + ScratchPad.recorded.should == :yield + end + + it "does not yield when #match? returns false" do + @guard.stub(:match?).and_return(false) + ruby_version_is('x.x.x') { ScratchPad.record :yield } + ScratchPad.recorded.should_not == :yield + end + + it "sets the name of the guard to :ruby_version_is" do + ruby_version_is("") { } + @guard.name.should == :ruby_version_is + end + + it "calls #unregister even when an exception is raised in the guard block" do + @guard.should_receive(:match?).and_return(true) + @guard.should_receive(:unregister) + lambda do + ruby_version_is("") { raise Exception } + end.should raise_error(Exception) + end +end diff --git a/spec/mspec/spec/helpers/argf_spec.rb b/spec/mspec/spec/helpers/argf_spec.rb new file mode 100644 index 0000000000..cf5eb0fe88 --- /dev/null +++ b/spec/mspec/spec/helpers/argf_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper' +require 'mspec/guards' +require 'mspec/helpers' + +describe Object, "#argf" do + before :each do + @saved_argv = ARGV.dup + @argv = [__FILE__] + end + + it "sets @argf to an instance of ARGF.class with the given argv" do + argf @argv do + @argf.should be_an_instance_of ARGF.class + @argf.filename.should == @argv.first + end + @argf.should be_nil + end + + it "does not alter ARGV nor ARGF" do + argf @argv do + end + ARGV.should == @saved_argv + ARGF.argv.should == @saved_argv + end + + it "does not close STDIN" do + argf ['-'] do + end + STDIN.should_not be_closed + end + + it "disallows nested calls" do + argf @argv do + lambda { argf @argv }.should raise_error + end + end +end diff --git a/spec/mspec/spec/helpers/argv_spec.rb b/spec/mspec/spec/helpers/argv_spec.rb new file mode 100644 index 0000000000..c3b21c7639 --- /dev/null +++ b/spec/mspec/spec/helpers/argv_spec.rb @@ -0,0 +1,27 @@ +require 'spec_helper' +require 'mspec/guards' +require 'mspec/helpers' + +describe Object, "#argv" do + before :each do + ScratchPad.clear + + @saved_argv = ARGV.dup + @argv = ["a", "b"] + end + + it "replaces and restores the value of ARGV" do + argv @argv + ARGV.should == @argv + argv :restore + ARGV.should == @saved_argv + end + + it "yields to the block after setting ARGV" do + argv @argv do + ScratchPad.record ARGV.dup + end + ScratchPad.recorded.should == @argv + ARGV.should == @saved_argv + end +end diff --git a/spec/mspec/spec/helpers/datetime_spec.rb b/spec/mspec/spec/helpers/datetime_spec.rb new file mode 100644 index 0000000000..8696c0c9c7 --- /dev/null +++ b/spec/mspec/spec/helpers/datetime_spec.rb @@ -0,0 +1,44 @@ +require 'spec_helper' +require 'mspec/guards' +require 'mspec/helpers' + +describe Object, "#new_datetime" do + it "returns a default DateTime instance" do + new_datetime.should == DateTime.new + end + + it "returns a DateTime instance with the specified year value" do + d = new_datetime :year => 1970 + d.year.should == 1970 + end + + it "returns a DateTime instance with the specified month value" do + d = new_datetime :month => 11 + d.mon.should == 11 + end + + it "returns a DateTime instance with the specified day value" do + d = new_datetime :day => 23 + d.day.should == 23 + end + + it "returns a DateTime instance with the specified hour value" do + d = new_datetime :hour => 10 + d.hour.should == 10 + end + + it "returns a DateTime instance with the specified minute value" do + d = new_datetime :minute => 10 + d.min.should == 10 + end + + it "returns a DateTime instance with the specified second value" do + d = new_datetime :second => 2 + d.sec.should == 2 + end + + it "returns a DateTime instance with the specified offset value" do + d = new_datetime :offset => Rational(3,24) + d.offset.should == Rational(3,24) + end +end diff --git a/spec/mspec/spec/helpers/fixture_spec.rb b/spec/mspec/spec/helpers/fixture_spec.rb new file mode 100644 index 0000000000..4dbdd092f1 --- /dev/null +++ b/spec/mspec/spec/helpers/fixture_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' +require 'mspec/guards' +require 'mspec/helpers' + +describe Object, "#fixture" do + before :each do + @dir = File.realpath("..", __FILE__) + end + + it "returns the expanded path to a fixture file" do + name = fixture(__FILE__, "subdir", "file.txt") + name.should == "#{@dir}/fixtures/subdir/file.txt" + end + + it "omits '/shared' if it is the suffix of the directory string" do + name = fixture("#{@dir}/shared/file.rb", "subdir", "file.txt") + name.should == "#{@dir}/fixtures/subdir/file.txt" + end + + it "does not append '/fixtures' if it is the suffix of the directory string" do + commands_dir = "#{File.dirname(@dir)}/commands" + name = fixture("#{commands_dir}/fixtures/file.rb", "subdir", "file.txt") + name.should == "#{commands_dir}/fixtures/subdir/file.txt" + end +end diff --git a/spec/mspec/spec/helpers/flunk_spec.rb b/spec/mspec/spec/helpers/flunk_spec.rb new file mode 100644 index 0000000000..7b1216d3f7 --- /dev/null +++ b/spec/mspec/spec/helpers/flunk_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' +require 'mspec/expectations/expectations' +require 'mspec/runner/mspec' +require 'mspec/guards' +require 'mspec/helpers' + +describe Object, "#flunk" do + before :each do + MSpec.stub(:actions) + MSpec.stub(:current).and_return(double("spec state").as_null_object) + end + + it "raises an SpecExpectationNotMetError unconditionally" do + lambda { flunk }.should raise_error(SpecExpectationNotMetError) + end + + it "accepts on argument for an optional message" do + lambda {flunk "test"}.should raise_error(SpecExpectationNotMetError) + end +end diff --git a/spec/mspec/spec/helpers/fs_spec.rb b/spec/mspec/spec/helpers/fs_spec.rb new file mode 100644 index 0000000000..5afa91ff58 --- /dev/null +++ b/spec/mspec/spec/helpers/fs_spec.rb @@ -0,0 +1,182 @@ +require 'spec_helper' +require 'mspec/guards' +require 'mspec/helpers' + +describe Object, "#cp" do + before :each do + @source = tmp("source.txt") + @copy = tmp("copied.txt") + + @contents = "This is a copy." + File.open(@source, "w") { |f| f.write @contents } + end + + after :each do + File.delete @source if File.exist? @source + File.delete @copy if File.exist? @copy + end + + it "copies a file" do + cp @source, @copy + data = IO.read(@copy) + data.should == @contents + data.should == IO.read(@source) + end +end + +describe Object, "#touch" do + before :all do + @name = tmp("touched.txt") + end + + after :each do + File.delete @name if File.exist? @name + end + + it "creates a file" do + touch @name + File.exist?(@name).should be_true + end + + it "accepts an optional mode argument" do + touch @name, "wb" + File.exist?(@name).should be_true + end + + it "overwrites an existing file" do + File.open(@name, "w") { |f| f.puts "used" } + File.size(@name).should > 0 + + touch @name + File.size(@name).should == 0 + end + + it "yields the open file if passed a block" do + touch(@name) { |f| f.write "touching" } + IO.read(@name).should == "touching" + end +end + +describe Object, "#touch" do + before :all do + @name = tmp("subdir/touched.txt") + end + + after :each do + rm_r File.dirname(@name) + end + + it "creates all the directories in the path to the file" do + touch @name + File.exist?(@name).should be_true + end +end + +describe Object, "#mkdir_p" do + before :all do + @dir1 = tmp("/nested") + @dir2 = @dir1 + "/directory" + @paths = [ @dir2, @dir1 ] + end + + after :each do + File.delete @dir1 if File.file? @dir1 + @paths.each { |path| Dir.rmdir path if File.directory? path } + end + + it "creates all the directories in a path" do + mkdir_p @dir2 + File.directory?(@dir2).should be_true + end + + it "raises an ArgumentError if a path component is a file" do + File.open(@dir1, "w") { |f| } + lambda { mkdir_p @dir2 }.should raise_error(ArgumentError) + end +end + +describe Object, "#rm_r" do + before :all do + @topdir = tmp("rm_r_tree") + @topfile = @topdir + "/file.txt" + @link = @topdir + "/file.lnk" + @socket = @topdir + "/socket.sck" + @subdir1 = @topdir + "/subdir1" + @subdir2 = @subdir1 + "/subdir2" + @subfile = @subdir1 + "/subfile.txt" + end + + before :each do + mkdir_p @subdir2 + touch @topfile + touch @subfile + end + + after :each do + File.delete @link if File.exist? @link or File.symlink? @link + File.delete @socket if File.exist? @socket + File.delete @subfile if File.exist? @subfile + File.delete @topfile if File.exist? @topfile + + Dir.rmdir @subdir2 if File.directory? @subdir2 + Dir.rmdir @subdir1 if File.directory? @subdir1 + Dir.rmdir @topdir if File.directory? @topdir + end + + it "raises an ArgumentError if the path is not prefixed by MSPEC_RM_PREFIX" do + lambda { rm_r "some_file.txt" }.should raise_error(ArgumentError) + end + + it "removes a single file" do + rm_r @subfile + File.exist?(@subfile).should be_false + end + + it "removes multiple files" do + rm_r @topfile, @subfile + File.exist?(@topfile).should be_false + File.exist?(@subfile).should be_false + end + + platform_is_not :windows do + it "removes a symlink to a file" do + File.symlink @topfile, @link + rm_r @link + File.exist?(@link).should be_false + end + + it "removes a symlink to a directory" do + File.symlink @subdir1, @link + rm_r @link + lambda do + File.lstat(@link) + end.should raise_error(Errno::ENOENT) + File.exist?(@subdir1).should be_true + end + + it "removes a dangling symlink" do + File.symlink "non_existent_file", @link + rm_r @link + lambda do + File.lstat(@link) + end.should raise_error(Errno::ENOENT) + end + + it "removes a socket" do + require 'socket' + UNIXServer.new(@socket).close + rm_r @socket + File.exist?(@socket).should be_false + end + end + + it "removes a single directory" do + rm_r @subdir2 + File.directory?(@subdir2).should be_false + end + + it "recursively removes a directory tree" do + rm_r @topdir + File.directory?(@topdir).should be_false + end +end diff --git a/spec/mspec/spec/helpers/io_spec.rb b/spec/mspec/spec/helpers/io_spec.rb new file mode 100644 index 0000000000..6dfd81ee56 --- /dev/null +++ b/spec/mspec/spec/helpers/io_spec.rb @@ -0,0 +1,174 @@ +require 'spec_helper' +require 'mspec/guards' +require 'mspec/helpers' + +describe IOStub do + before :each do + @out = IOStub.new + @sep = $\ + end + + after :each do + $\ = @sep + end + + it "provides a write method" do + @out.write "this" + @out.should == "this" + end + + it "concatenates the arguments sent to write" do + @out.write "flim ", "flam" + @out.should == "flim flam" + end + + it "provides a print method that appends the default separator" do + $\ = " [newline] " + @out.print "hello" + @out.print "world" + @out.should == "hello [newline] world [newline] " + end + + it "provides a puts method that appends the default separator" do + @out.puts "hello", 1, 2, 3 + @out.should == "hello\n1\n2\n3\n" + end + + it "provides a puts method that appends separator if argument not given" do + @out.puts + @out.should == "\n" + end + + it "provides a printf method" do + @out.printf "%-10s, %03d, %2.1f", "test", 42, 4.2 + @out.should == "test , 042, 4.2" + end + + it "provides a flush method that does nothing and returns self" do + @out.flush.should == @out + end +end + +describe Object, "#new_fd" do + before :each do + @name = tmp("io_specs") + @io = nil + end + + after :each do + @io.close if @io and not @io.closed? + rm_r @name + end + + it "returns a Fixnum that can be used to create an IO instance" do + fd = new_fd @name + fd.should be_an_instance_of(Fixnum) + + @io = IO.new fd, fmode('w:utf-8') + @io.sync = true + @io.print "io data" + + IO.read(@name).should == "io data" + end + + it "accepts an options Hash" do + FeatureGuard.stub(:enabled?).and_return(true) + fd = new_fd @name, { :mode => 'w:utf-8' } + fd.should be_an_instance_of(Fixnum) + + @io = IO.new fd, fmode('w:utf-8') + @io.sync = true + @io.print "io data" + + IO.read(@name).should == "io data" + end + + it "raises an ArgumentError if the options Hash does not include :mode" do + FeatureGuard.stub(:enabled?).and_return(true) + lambda { new_fd @name, { :encoding => "utf-8" } }.should raise_error(ArgumentError) + end +end + +describe Object, "#new_io" do + before :each do + @name = tmp("io_specs.txt") + end + + after :each do + @io.close if @io and !@io.closed? + rm_r @name + end + + it "returns an IO instance" do + @io = new_io @name + @io.should be_an_instance_of(IO) + end + + it "opens the IO for reading if passed 'r'" do + touch(@name) { |f| f.print "io data" } + @io = new_io @name, "r" + @io.read.should == "io data" + lambda { @io.puts "more data" }.should raise_error(IOError) + end + + it "opens the IO for writing if passed 'w'" do + @io = new_io @name, "w" + @io.sync = true + + @io.print "io data" + IO.read(@name).should == "io data" + end + + it "opens the IO for reading if passed { :mode => 'r' }" do + touch(@name) { |f| f.print "io data" } + @io = new_io @name, { :mode => "r" } + @io.read.should == "io data" + lambda { @io.puts "more data" }.should raise_error(IOError) + end + + it "opens the IO for writing if passed { :mode => 'w' }" do + @io = new_io @name, { :mode => "w" } + @io.sync = true + + @io.print "io data" + IO.read(@name).should == "io data" + end +end + +describe Object, "#fmode" do + it "returns the argument unmodified if :encoding feature is enabled" do + FeatureGuard.should_receive(:enabled?).with(:encoding).and_return(true) + fmode("rb:binary:utf-8").should == "rb:binary:utf-8" + end + + it "returns only the file access mode if :encoding feature is not enabled" do + FeatureGuard.should_receive(:enabled?).with(:encoding).and_return(false) + fmode("rb:binary:utf-8").should == "rb" + end +end + +describe Object, "#options_or_mode" do + describe "if passed a Hash" do + it "returns a mode string if :encoding feature is not enabled" do + FeatureGuard.should_receive(:enabled?).with(:encoding).twice.and_return(false) + options_or_mode(:mode => "rb:binary").should == "rb" + end + + it "returns a Hash if :encoding feature is enabled" do + FeatureGuard.should_receive(:enabled?).with(:encoding).and_return(true) + options_or_mode(:mode => "rb:utf-8").should == { :mode => "rb:utf-8" } + end + end + + describe "if passed a String" do + it "returns only the file access mode if :encoding feature is not enabled" do + FeatureGuard.should_receive(:enabled?).with(:encoding).and_return(false) + options_or_mode("rb:binary:utf-8").should == "rb" + end + + it "returns the argument unmodified if :encoding feature is enabled" do + FeatureGuard.should_receive(:enabled?).with(:encoding).and_return(true) + options_or_mode("rb:binary:utf-8").should == "rb:binary:utf-8" + end + end +end diff --git a/spec/mspec/spec/helpers/mock_to_path_spec.rb b/spec/mspec/spec/helpers/mock_to_path_spec.rb new file mode 100644 index 0000000000..464e7e5440 --- /dev/null +++ b/spec/mspec/spec/helpers/mock_to_path_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' +require 'mspec/guards' +require 'mspec/helpers' + +describe Object, "#mock_to_path" do + it "returns an object that responds to #to_path" do + obj = mock_to_path("foo") + obj.should be_a(MockObject) + obj.should respond_to(:to_path) + obj.to_path + end + + it "returns the provided path when #to_path is called" do + obj = mock_to_path("/tmp/foo") + obj.to_path.should == "/tmp/foo" + end +end diff --git a/spec/mspec/spec/helpers/numeric_spec.rb b/spec/mspec/spec/helpers/numeric_spec.rb new file mode 100644 index 0000000000..2ea56e5961 --- /dev/null +++ b/spec/mspec/spec/helpers/numeric_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' +require 'mspec/guards' +require 'mspec/helpers' + +describe Object, "#bignum_value" do + it "returns a value that is an instance of Bignum on any platform" do + bignum_value.should == 0x8000_0000_0000_0000 + end + + it "returns the default value incremented by the argument" do + bignum_value(42).should == 0x8000_0000_0000_002a + end +end + +describe Object, "#nan_value" do + it "returns NaN" do + nan_value.nan?.should be_true + end +end + +describe Object, "#infinity_value" do + it "returns Infinity" do + infinity_value.infinite?.should == 1 + end +end diff --git a/spec/mspec/spec/helpers/ruby_exe_spec.rb b/spec/mspec/spec/helpers/ruby_exe_spec.rb new file mode 100644 index 0000000000..debfc3b1ca --- /dev/null +++ b/spec/mspec/spec/helpers/ruby_exe_spec.rb @@ -0,0 +1,220 @@ +require 'spec_helper' +require 'mspec/guards' +require 'mspec/helpers' +require 'rbconfig' + +class RubyExeSpecs +end + +describe "#ruby_exe_options" do + before :all do + @verbose = $VERBOSE + $VERBOSE = nil + + @ruby_name = Object.const_get :RUBY_NAME + @ruby_exe_env = ENV['RUBY_EXE'] + + @script = RubyExeSpecs.new + end + + after :all do + Object.const_set :RUBY_NAME, @ruby_name + ENV['RUBY_EXE'] = @ruby_exe_env + $VERBOSE = @verbose + end + + before :each do + @script = RubyExeSpecs.new + end + + it "returns ENV['RUBY_EXE'] when passed :env" do + ENV['RUBY_EXE'] = "kowabunga" + @script.ruby_exe_options(:env).should == "kowabunga" + end + + it "returns 'bin/jruby' when passed :engine and RUBY_NAME is 'jruby'" do + Object.const_set :RUBY_NAME, 'jruby' + @script.ruby_exe_options(:engine).should == 'bin/jruby' + end + + it "returns 'bin/rbx' when passed :engine, RUBY_NAME is 'rbx'" do + Object.const_set :RUBY_NAME, 'rbx' + @script.ruby_exe_options(:engine).should == 'bin/rbx' + end + + it "returns 'ir' when passed :engine and RUBY_NAME is 'ironruby'" do + Object.const_set :RUBY_NAME, 'ironruby' + @script.ruby_exe_options(:engine).should == 'ir' + end + + it "returns 'maglev-ruby' when passed :engine and RUBY_NAME is 'maglev'" do + Object.const_set :RUBY_NAME, 'maglev' + @script.ruby_exe_options(:engine).should == 'maglev-ruby' + end + + it "returns 'topaz' when passed :engine and RUBY_NAME is 'topaz'" do + Object.const_set :RUBY_NAME, 'topaz' + @script.ruby_exe_options(:engine).should == 'topaz' + end + + it "returns RUBY_NAME + $(EXEEXT) when passed :name" do + bin = RUBY_NAME + (RbConfig::CONFIG['EXEEXT'] || RbConfig::CONFIG['exeext'] || '') + name = File.join ".", bin + @script.ruby_exe_options(:name).should == name + end + + it "returns $(bindir)/$(RUBY_INSTALL_NAME) + $(EXEEXT) when passed :install_name" do + bin = RbConfig::CONFIG['RUBY_INSTALL_NAME'] + (RbConfig::CONFIG['EXEEXT'] || RbConfig::CONFIG['exeext'] || '') + name = File.join RbConfig::CONFIG['bindir'], bin + @script.ruby_exe_options(:install_name).should == name + end +end + +describe "#resolve_ruby_exe" do + before :all do + @verbose = $VERBOSE + $VERBOSE = nil + + @name = "ruby_spec_exe" + end + + before :each do + @script = RubyExeSpecs.new + end + + after :all do + $VERBOSE = @verbose + end + + it "returns the value returned by #ruby_exe_options if it exists and is executable" do + @script.should_receive(:ruby_exe_options).and_return(@name) + File.should_receive(:file?).with(@name).and_return(true) + File.should_receive(:executable?).with(@name).and_return(true) + File.should_receive(:expand_path).with(@name).and_return(@name) + @script.resolve_ruby_exe.should == @name + end + + it "expands the path portion of the result of #ruby_exe_options" do + @script.should_receive(:ruby_exe_options).and_return("#{@name}") + File.should_receive(:file?).with(@name).and_return(true) + File.should_receive(:executable?).with(@name).and_return(true) + File.should_receive(:expand_path).with(@name).and_return("/usr/bin/#{@name}") + @script.resolve_ruby_exe.should == "/usr/bin/#{@name}" + end + + it "adds the flags after the executable" do + @name = 'bin/rbx' + @script.should_receive(:ruby_exe_options).and_return(@name) + File.should_receive(:file?).with(@name).and_return(true) + File.should_receive(:executable?).with(@name).and_return(true) + File.should_receive(:expand_path).with(@name).and_return(@name) + + ENV.should_receive(:[]).with("RUBY_FLAGS").and_return('-X19') + @script.resolve_ruby_exe.should == 'bin/rbx -X19' + end + + it "raises an exception if no exe is found" do + File.should_receive(:file?).at_least(:once).and_return(false) + lambda { + @script.resolve_ruby_exe + }.should raise_error(Exception) + end +end + +describe Object, "#ruby_cmd" do + before :all do + @verbose = $VERBOSE + $VERBOSE = nil + + @ruby_exe = Object.const_get :RUBY_EXE + Object.const_set :RUBY_EXE, 'ruby_spec_exe -w -Q' + + @file = "some/ruby/file.rb" + @code = %(some "real" 'ruby' code) + + @script = RubyExeSpecs.new + end + + after :all do + Object.const_set :RUBY_EXE, @ruby_exe + $VERBOSE = @verbose + end + + it "returns a command that runs the given file if it is a file that exists" do + File.should_receive(:exist?).with(@file).and_return(true) + @script.ruby_cmd(@file).should == "ruby_spec_exe -w -Q some/ruby/file.rb" + end + + it "includes the given options and arguments with a file" do + File.should_receive(:exist?).with(@file).and_return(true) + @script.ruby_cmd(@file, :options => "-w -Cdir", :args => "< file.txt").should == + "ruby_spec_exe -w -Q -w -Cdir some/ruby/file.rb < file.txt" + end + + it "includes the given options and arguments with -e" do + File.should_receive(:exist?).with(@code).and_return(false) + @script.ruby_cmd(@code, :options => "-W0 -Cdir", :args => "< file.txt").should == + %(ruby_spec_exe -w -Q -W0 -Cdir -e "some \\"real\\" 'ruby' code" < file.txt) + end + + it "returns a command with options and arguments but without code or file" do + @script.ruby_cmd(nil, :options => "-c", :args => "> file.txt").should == + "ruby_spec_exe -w -Q -c > file.txt" + end +end + +describe Object, "#ruby_exe" do + before :all do + @script = RubyExeSpecs.new + end + + before :each do + @script.stub(:`) + end + + it "executes (using `) the result of calling #ruby_cmd with the given arguments" do + code = "code" + options = {} + @script.should_receive(:ruby_cmd).and_return("ruby_cmd") + @script.should_receive(:`).with("ruby_cmd") + @script.ruby_exe(code, options) + end + + describe "with :dir option" do + it "is deprecated" do + lambda { + @script.ruby_exe nil, :dir => "tmp" + }.should raise_error(/no longer supported, use Dir\.chdir/) + end + end + + describe "with :env option" do + it "preserves the values of existing ENV keys" do + ENV["ABC"] = "123" + ENV.stub(:[]) + ENV.should_receive(:[]).with("ABC") + @script.ruby_exe nil, :env => { :ABC => "xyz" } + end + + it "adds the :env entries to ENV" do + ENV.should_receive(:[]=).with("ABC", "xyz") + @script.ruby_exe nil, :env => { :ABC => "xyz" } + end + + it "deletes the :env entries in ENV when an exception is raised" do + ENV.should_receive(:delete).with("XYZ") + @script.ruby_exe nil, :env => { :XYZ => "xyz" } + end + + it "resets the values of existing ENV keys when an exception is raised" do + ENV["ABC"] = "123" + ENV.should_receive(:[]=).with("ABC", "xyz") + ENV.should_receive(:[]=).with("ABC", "123") + + @script.should_receive(:`).and_raise(Exception) + lambda do + @script.ruby_exe nil, :env => { :ABC => "xyz" } + end.should raise_error(Exception) + end + end +end diff --git a/spec/mspec/spec/helpers/scratch_spec.rb b/spec/mspec/spec/helpers/scratch_spec.rb new file mode 100644 index 0000000000..6a9eb2cf73 --- /dev/null +++ b/spec/mspec/spec/helpers/scratch_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' +require 'mspec/guards' +require 'mspec/helpers' + +describe ScratchPad do + it "records an object and returns a previously recorded object" do + ScratchPad.record :this + ScratchPad.recorded.should == :this + end + + it "clears the recorded object" do + ScratchPad.record :that + ScratchPad.recorded.should == :that + ScratchPad.clear + ScratchPad.recorded.should == nil + end + + it "provides a convenience shortcut to append to a previously recorded object" do + ScratchPad.record [] + ScratchPad << :new + ScratchPad << :another + ScratchPad.recorded.should == [:new, :another] + end +end diff --git a/spec/mspec/spec/helpers/tmp_spec.rb b/spec/mspec/spec/helpers/tmp_spec.rb new file mode 100644 index 0000000000..afadc7f51c --- /dev/null +++ b/spec/mspec/spec/helpers/tmp_spec.rb @@ -0,0 +1,27 @@ +require 'spec_helper' +require 'mspec/guards' +require 'mspec/helpers' + +describe Object, "#tmp" do + before :all do + @dir = "#{File.expand_path(Dir.pwd)}/rubyspec_temp" + end + + it "returns a name relative to the current working directory" do + tmp("test.txt").should == "#{@dir}/#{SPEC_TEMP_UNIQUIFIER}-test.txt" + end + + it "returns a 'unique' name on repeated calls" do + a = tmp("text.txt") + b = tmp("text.txt") + a.should_not == b + end + + it "does not 'uniquify' the name if requested not to" do + tmp("test.txt", false).should == "#{@dir}/test.txt" + end + + it "returns the name of the temporary directory when passed an empty string" do + tmp("").should == "#{@dir}/" + end +end diff --git a/spec/mspec/spec/integration/interpreter_spec.rb b/spec/mspec/spec/integration/interpreter_spec.rb new file mode 100644 index 0000000000..b6fa6859d1 --- /dev/null +++ b/spec/mspec/spec/integration/interpreter_spec.rb @@ -0,0 +1,18 @@ +require 'spec_helper' + +describe "The interpreter passed with -t" do + it "is used in subprocess" do + fixtures = "spec/fixtures" + interpreter = "#{fixtures}/my_ruby" + out, ret = run_mspec("run", "#{fixtures}/print_interpreter_spec.rb -t #{interpreter}") + out = out.lines.map(&:chomp).reject { |line| + line == 'RUBY_DESCRIPTION' + }.take(3) + out.should == [ + interpreter, + interpreter, + "CWD/#{interpreter}" + ] + ret.success?.should == true + end +end diff --git a/spec/mspec/spec/integration/run_spec.rb b/spec/mspec/spec/integration/run_spec.rb new file mode 100644 index 0000000000..93d2ef8b68 --- /dev/null +++ b/spec/mspec/spec/integration/run_spec.rb @@ -0,0 +1,52 @@ +require 'spec_helper' + +describe "Running mspec" do + a_spec_output = <' +CWD/spec/fixtures/a_spec.rb:2:in `' +CWD/bin/mspec-run:7:in `
    ' + +2) +Foo#bar fails ERROR +RuntimeError: failure +CWD/spec/fixtures/a_spec.rb:12:in `block (2 levels) in ' +CWD/spec/fixtures/a_spec.rb:2:in `' +CWD/bin/mspec-run:7:in `
    ' + +Finished in D.DDDDDD seconds +EOS + + a_stats = "1 file, 3 examples, 2 expectations, 1 failure, 1 error, 0 tagged\n" + ab_stats = "2 files, 4 examples, 3 expectations, 1 failure, 1 error, 0 tagged\n" + + it "runs the specs" do + fixtures = "spec/fixtures" + out, ret = run_mspec("run", "#{fixtures}/a_spec.rb") + out.should == "RUBY_DESCRIPTION\n.FE\n#{a_spec_output}\n#{a_stats}" + ret.success?.should == false + end + + it "directly with mspec-run runs the specs" do + fixtures = "spec/fixtures" + out, ret = run_mspec("-run", "#{fixtures}/a_spec.rb") + out.should == "RUBY_DESCRIPTION\n.FE\n#{a_spec_output}\n#{a_stats}" + ret.success?.should == false + end + + it "runs the specs in parallel with -j" do + fixtures = "spec/fixtures" + out, ret = run_mspec("run", "-j #{fixtures}/a_spec.rb #{fixtures}/b_spec.rb") + progress_bar = + "\r[/ | 0% | 00:00:00] \e[0;32m 0F \e[0;32m 0E\e[0m " + + "\r[- | ==================50% | 00:00:00] \e[0;32m 0F \e[0;32m 0E\e[0m " + + "\r[\\ | ==================100%================== | 00:00:00] \e[0;32m 0F \e[0;32m 0E\e[0m " + out.should == "RUBY_DESCRIPTION\n#{progress_bar}\n#{a_spec_output}\n#{ab_stats}" + ret.success?.should == false + end +end diff --git a/spec/mspec/spec/integration/tag_spec.rb b/spec/mspec/spec/integration/tag_spec.rb new file mode 100644 index 0000000000..d980769043 --- /dev/null +++ b/spec/mspec/spec/integration/tag_spec.rb @@ -0,0 +1,63 @@ +# encoding: utf-8 +require 'spec_helper' + +describe "Running mspec tag" do + before :all do + FileUtils.rm_rf 'spec/fixtures/tags' + end + + after :all do + FileUtils.rm_rf 'spec/fixtures/tags' + end + + it "tags the failing specs" do + fixtures = "spec/fixtures" + out, ret = run_mspec("tag", "--add fails --fail #{fixtures}/tagging_spec.rb") + out.should == <' +CWD/spec/fixtures/tagging_spec.rb:3:in `' +CWD/bin/mspec-tag:7:in `
    ' + +2) +Tag#me érròrs in unicode FAILED +Expected 1 + to equal 2 + +CWD/spec/fixtures/tagging_spec.rb:13:in `block (2 levels) in ' +CWD/spec/fixtures/tagging_spec.rb:3:in `' +CWD/bin/mspec-tag:7:in `
    ' + +Finished in D.DDDDDD seconds + +1 file, 3 examples, 3 expectations, 2 failures, 0 errors, 0 tagged +EOS + ret.success?.should == false + end + + it "does not run already tagged specs" do + fixtures = "spec/fixtures" + out, ret = run_mspec("run", "--excl-tag fails #{fixtures}/tagging_spec.rb") + out.should == < operator" do + it "raises an SpecExpectationNotMetError when expected > actual returns false" do + lambda { + SpecPositiveOperatorMatcher.new(4) > 5 + }.should raise_error(SpecExpectationNotMetError) + end + + it "provides a failure message that 'Expected x to be greater than y'" do + SpecExpectation.should_receive(:fail_with).with( + "Expected 4\n", "to be greater than 5\n") + SpecPositiveOperatorMatcher.new(4) > 5 + end + + it "does not raise an exception when expected > actual returns true" do + SpecPositiveOperatorMatcher.new(5) > 4 + end +end + +describe SpecPositiveOperatorMatcher, ">= operator" do + it "raises an SpecExpectationNotMetError when expected >= actual returns false" do + lambda { + SpecPositiveOperatorMatcher.new(4) >= 5 + }.should raise_error(SpecExpectationNotMetError) + end + + it "provides a failure message that 'Expected x to be greater than or equal to y'" do + SpecExpectation.should_receive(:fail_with).with( + "Expected 4\n", "to be greater than or equal to 5\n") + SpecPositiveOperatorMatcher.new(4) >= 5 + end + + it "does not raise an exception when expected > actual returns true" do + SpecPositiveOperatorMatcher.new(5) >= 4 + SpecPositiveOperatorMatcher.new(5) >= 5 + end +end + +describe SpecPositiveOperatorMatcher, "< operater" do + it "raises an SpecExpectationNotMetError when expected < actual returns false" do + lambda { + SpecPositiveOperatorMatcher.new(5) < 4 + }.should raise_error(SpecExpectationNotMetError) + end + + it "provides a failure message that 'Expected x to be less than y'" do + SpecExpectation.should_receive(:fail_with).with("Expected 5\n", "to be less than 4\n") + SpecPositiveOperatorMatcher.new(5) < 4 + end + + it "does not raise an exception when expected < actual returns true" do + SpecPositiveOperatorMatcher.new(4) < 5 + end +end + +describe SpecPositiveOperatorMatcher, "<= operater" do + it "raises an SpecExpectationNotMetError when expected < actual returns false" do + lambda { + SpecPositiveOperatorMatcher.new(5) <= 4 + }.should raise_error(SpecExpectationNotMetError) + end + + it "provides a failure message that 'Expected x to be less than or equal to y'" do + SpecExpectation.should_receive(:fail_with).with( + "Expected 5\n", "to be less than or equal to 4\n") + SpecPositiveOperatorMatcher.new(5) <= 4 + end + + it "does not raise an exception when expected < actual returns true" do + SpecPositiveOperatorMatcher.new(4) <= 5 + SpecPositiveOperatorMatcher.new(4) <= 4 + end +end + +describe SpecNegativeOperatorMatcher, "== operator" do + it "raises an SpecExpectationNotMetError when expected == actual returns true" do + lambda { + SpecNegativeOperatorMatcher.new(1) == 1 + }.should raise_error(SpecExpectationNotMetError) + end + + it "provides a failure message that 'Expected x not to equal y'" do + SpecExpectation.should_receive(:fail_with).with("Expected 1\n", "not to equal 1\n") + SpecNegativeOperatorMatcher.new(1) == 1 + end + + it "does not raise an exception when expected == actual returns false" do + SpecNegativeOperatorMatcher.new(1) == 2 + end +end + +describe SpecNegativeOperatorMatcher, "=~ operator" do + it "raises an SpecExpectationNotMetError when expected =~ actual returns true" do + lambda { + SpecNegativeOperatorMatcher.new('real') =~ /real/ + }.should raise_error(SpecExpectationNotMetError) + end + + it "provides a failure message that 'Expected \"x\" not to match /y/'" do + SpecExpectation.should_receive(:fail_with).with( + "Expected \"real\"\n", "not to match /real/\n") + SpecNegativeOperatorMatcher.new('real') =~ /real/ + end + + it "does not raise an exception when expected =~ actual returns false" do + SpecNegativeOperatorMatcher.new('real') =~ /fake/ + end +end + +describe SpecNegativeOperatorMatcher, "< operator" do + it "raises an SpecExpectationNotMetError when expected < actual returns true" do + lambda { + SpecNegativeOperatorMatcher.new(4) < 5 + }.should raise_error(SpecExpectationNotMetError) + end + + it "provides a failure message that 'Expected x not to be less than y'" do + SpecExpectation.should_receive(:fail_with).with( + "Expected 4\n", "not to be less than 5\n") + SpecNegativeOperatorMatcher.new(4) < 5 + end + + it "does not raise an exception when expected < actual returns false" do + SpecNegativeOperatorMatcher.new(5) < 4 + end +end + +describe SpecNegativeOperatorMatcher, "<= operator" do + it "raises an SpecExpectationNotMetError when expected <= actual returns true" do + lambda { + SpecNegativeOperatorMatcher.new(4) <= 5 + }.should raise_error(SpecExpectationNotMetError) + lambda { + SpecNegativeOperatorMatcher.new(5) <= 5 + }.should raise_error(SpecExpectationNotMetError) + end + + it "provides a failure message that 'Expected x not to be less than or equal to y'" do + SpecExpectation.should_receive(:fail_with).with( + "Expected 4\n", "not to be less than or equal to 5\n") + SpecNegativeOperatorMatcher.new(4) <= 5 + end + + it "does not raise an exception when expected <= actual returns false" do + SpecNegativeOperatorMatcher.new(5) <= 4 + end +end + +describe SpecNegativeOperatorMatcher, "> operator" do + it "raises an SpecExpectationNotMetError when expected > actual returns true" do + lambda { + SpecNegativeOperatorMatcher.new(5) > 4 + }.should raise_error(SpecExpectationNotMetError) + end + + it "provides a failure message that 'Expected x not to be greater than y'" do + SpecExpectation.should_receive(:fail_with).with( + "Expected 5\n", "not to be greater than 4\n") + SpecNegativeOperatorMatcher.new(5) > 4 + end + + it "does not raise an exception when expected > actual returns false" do + SpecNegativeOperatorMatcher.new(4) > 5 + end +end + +describe SpecNegativeOperatorMatcher, ">= operator" do + it "raises an SpecExpectationNotMetError when expected >= actual returns true" do + lambda { + SpecNegativeOperatorMatcher.new(5) >= 4 + }.should raise_error(SpecExpectationNotMetError) + lambda { + SpecNegativeOperatorMatcher.new(5) >= 5 + }.should raise_error(SpecExpectationNotMetError) + end + + it "provides a failure message that 'Expected x not to be greater than or equal to y'" do + SpecExpectation.should_receive(:fail_with).with( + "Expected 5\n", "not to be greater than or equal to 4\n") + SpecNegativeOperatorMatcher.new(5) >= 4 + end + + it "does not raise an exception when expected >= actual returns false" do + SpecNegativeOperatorMatcher.new(4) >= 5 + end +end diff --git a/spec/mspec/spec/matchers/be_an_instance_of_spec.rb b/spec/mspec/spec/matchers/be_an_instance_of_spec.rb new file mode 100644 index 0000000000..7f2126df7d --- /dev/null +++ b/spec/mspec/spec/matchers/be_an_instance_of_spec.rb @@ -0,0 +1,50 @@ +require 'spec_helper' +require 'mspec/expectations/expectations' +require 'mspec/matchers' + +module BeAnInOfSpecs + class A + end + + class B < A + end + + class C < B + end +end + +describe BeAnInstanceOfMatcher do + it "matches when actual is an instance_of? expected" do + a = BeAnInOfSpecs::A.new + BeAnInstanceOfMatcher.new(BeAnInOfSpecs::A).matches?(a).should be_true + + b = BeAnInOfSpecs::B.new + BeAnInstanceOfMatcher.new(BeAnInOfSpecs::B).matches?(b).should be_true + end + + it "does not match when actual is not an instance_of? expected" do + a = BeAnInOfSpecs::A.new + BeAnInstanceOfMatcher.new(BeAnInOfSpecs::B).matches?(a).should be_false + + b = BeAnInOfSpecs::B.new + BeAnInstanceOfMatcher.new(BeAnInOfSpecs::A).matches?(b).should be_false + + c = BeAnInOfSpecs::C.new + BeAnInstanceOfMatcher.new(BeAnInOfSpecs::A).matches?(c).should be_false + BeAnInstanceOfMatcher.new(BeAnInOfSpecs::B).matches?(c).should be_false + end + + it "provides a useful failure message" do + matcher = BeAnInstanceOfMatcher.new(Numeric) + matcher.matches?("string") + matcher.failure_message.should == [ + "Expected \"string\" (String)", "to be an instance of Numeric"] + end + + it "provides a useful negative failure message" do + matcher = BeAnInstanceOfMatcher.new(Numeric) + matcher.matches?(4.0) + matcher.negative_failure_message.should == [ + "Expected 4.0 (Float)", "not to be an instance of Numeric"] + end +end diff --git a/spec/mspec/spec/matchers/be_ancestor_of_spec.rb b/spec/mspec/spec/matchers/be_ancestor_of_spec.rb new file mode 100644 index 0000000000..c6bd1a26c7 --- /dev/null +++ b/spec/mspec/spec/matchers/be_ancestor_of_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' +require 'mspec/expectations/expectations' +require 'mspec/matchers' + +class Parent; end +class Child < Parent; end + +describe BeAncestorOfMatcher do + it "matches when actual is an ancestor of expected" do + BeAncestorOfMatcher.new(Child).matches?(Parent).should == true + end + + it "does not match when actual is not an ancestor of expected" do + BeAncestorOfMatcher.new(Parent).matches?(Child).should == false + end + + it "provides a useful failure message" do + matcher = BeAncestorOfMatcher.new(Parent) + matcher.matches?(Child) + matcher.failure_message.should == ["Expected Child", "to be an ancestor of Parent"] + end + + it "provides a useful negative failure message" do + matcher = BeAncestorOfMatcher.new(Child) + matcher.matches?(Parent) + matcher.negative_failure_message.should == ["Expected Parent", "not to be an ancestor of Child"] + end +end diff --git a/spec/mspec/spec/matchers/be_close_spec.rb b/spec/mspec/spec/matchers/be_close_spec.rb new file mode 100644 index 0000000000..3ced61dc7a --- /dev/null +++ b/spec/mspec/spec/matchers/be_close_spec.rb @@ -0,0 +1,46 @@ +require 'spec_helper' +require 'mspec/expectations/expectations' +require 'mspec/matchers' + +# Adapted from RSpec 1.0.8 +describe BeCloseMatcher do + it "matches when actual == expected" do + BeCloseMatcher.new(5.0, 0.5).matches?(5.0).should == true + end + + it "matches when actual < (expected + tolerance)" do + BeCloseMatcher.new(5.0, 0.5).matches?(5.49).should == true + end + + it "matches when actual > (expected - tolerance)" do + BeCloseMatcher.new(5.0, 0.5).matches?(4.51).should == true + end + + it "does not match when actual == (expected + tolerance)" do + BeCloseMatcher.new(5.0, 0.5).matches?(5.5).should == false + end + + it "does not match when actual == (expected - tolerance)" do + BeCloseMatcher.new(5.0, 0.5).matches?(4.5).should == false + end + + it "does not match when actual < (expected - tolerance)" do + BeCloseMatcher.new(5.0, 0.5).matches?(4.49).should == false + end + + it "does not match when actual > (expected + tolerance)" do + BeCloseMatcher.new(5.0, 0.5).matches?(5.51).should == false + end + + it "provides a useful failure message" do + matcher = BeCloseMatcher.new(5.0, 0.5) + matcher.matches?(5.5) + matcher.failure_message.should == ["Expected 5.0", "to be within +/- 0.5 of 5.5"] + end + + it "provides a useful negative failure message" do + matcher = BeCloseMatcher.new(5.0, 0.5) + matcher.matches?(5.0) + matcher.negative_failure_message.should == ["Expected 5.0", "not to be within +/- 0.5 of 5.0"] + end +end diff --git a/spec/mspec/spec/matchers/be_computed_by_spec.rb b/spec/mspec/spec/matchers/be_computed_by_spec.rb new file mode 100644 index 0000000000..9833e211a4 --- /dev/null +++ b/spec/mspec/spec/matchers/be_computed_by_spec.rb @@ -0,0 +1,42 @@ +require 'spec_helper' +require 'mspec/matchers' + +describe BeComputedByMatcher do + it "matches when all entries in the Array compute" do + array = [ [65, "A"], + [90, "Z"] ] + BeComputedByMatcher.new(:chr).matches?(array).should be_true + end + + it "matches when all entries in the Array with arguments compute" do + array = [ [1, 2, 3], + [2, 4, 6] ] + BeComputedByMatcher.new(:+).matches?(array).should be_true + end + + it "does not match when any entry in the Array does not compute" do + array = [ [65, "A" ], + [91, "Z" ] ] + BeComputedByMatcher.new(:chr).matches?(array).should be_false + end + + it "accepts an argument list to apply to each method call" do + array = [ [65, "1000001" ], + [90, "1011010" ] ] + BeComputedByMatcher.new(:to_s, 2).matches?(array).should be_true + end + + it "does not match when any entry in the Array with arguments does not compute" do + array = [ [1, 2, 3], + [2, 4, 7] ] + BeComputedByMatcher.new(:+).matches?(array).should be_false + end + + it "provides a useful failure message" do + array = [ [65, "A" ], + [91, "Z" ] ] + matcher = BeComputedByMatcher.new(:chr) + matcher.matches?(array) + matcher.failure_message.should == ["Expected \"Z\"", "to be computed by 91.chr (computed \"[\" instead)"] + end +end diff --git a/spec/mspec/spec/matchers/be_empty_spec.rb b/spec/mspec/spec/matchers/be_empty_spec.rb new file mode 100644 index 0000000000..cb8663f5ee --- /dev/null +++ b/spec/mspec/spec/matchers/be_empty_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' +require 'mspec/expectations/expectations' +require 'mspec/matchers' + +describe BeEmptyMatcher do + it "matches when actual is empty" do + BeEmptyMatcher.new.matches?("").should == true + end + + it "does not match when actual is not empty" do + BeEmptyMatcher.new.matches?([10]).should == false + end + + it "provides a useful failure message" do + matcher = BeEmptyMatcher.new + matcher.matches?("not empty string") + matcher.failure_message.should == ["Expected \"not empty string\"", "to be empty"] + end + + it "provides a useful negative failure message" do + matcher = BeEmptyMatcher.new + matcher.matches?("") + matcher.negative_failure_message.should == ["Expected \"\"", "not to be empty"] + end +end + diff --git a/spec/mspec/spec/matchers/be_false_spec.rb b/spec/mspec/spec/matchers/be_false_spec.rb new file mode 100644 index 0000000000..31afd24ebc --- /dev/null +++ b/spec/mspec/spec/matchers/be_false_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' +require 'mspec/expectations/expectations' +require 'mspec/matchers' + +describe BeFalseMatcher do + it "matches when actual is false" do + BeFalseMatcher.new.matches?(false).should == true + end + + it "does not match when actual is not false" do + BeFalseMatcher.new.matches?("").should == false + BeFalseMatcher.new.matches?(true).should == false + BeFalseMatcher.new.matches?(nil).should == false + BeFalseMatcher.new.matches?(0).should == false + end + + it "provides a useful failure message" do + matcher = BeFalseMatcher.new + matcher.matches?("some string") + matcher.failure_message.should == ["Expected \"some string\"", "to be false"] + end + + it "provides a useful negative failure message" do + matcher = BeFalseMatcher.new + matcher.matches?(false) + matcher.negative_failure_message.should == ["Expected false", "not to be false"] + end +end diff --git a/spec/mspec/spec/matchers/be_kind_of_spec.rb b/spec/mspec/spec/matchers/be_kind_of_spec.rb new file mode 100644 index 0000000000..554ae6aa82 --- /dev/null +++ b/spec/mspec/spec/matchers/be_kind_of_spec.rb @@ -0,0 +1,31 @@ +require 'spec_helper' +require 'mspec/expectations/expectations' +require 'mspec/matchers' + +describe BeKindOfMatcher do + it "matches when actual is a kind_of? expected" do + BeKindOfMatcher.new(Integer).matches?(1).should == true + BeKindOfMatcher.new(Fixnum).matches?(2).should == true + BeKindOfMatcher.new(Regexp).matches?(/m/).should == true + end + + it "does not match when actual is not a kind_of? expected" do + BeKindOfMatcher.new(Integer).matches?(1.5).should == false + BeKindOfMatcher.new(String).matches?(:a).should == false + BeKindOfMatcher.new(Hash).matches?([]).should == false + end + + it "provides a useful failure message" do + matcher = BeKindOfMatcher.new(Numeric) + matcher.matches?('string') + matcher.failure_message.should == [ + "Expected \"string\" (String)", "to be kind of Numeric"] + end + + it "provides a useful negative failure message" do + matcher = BeKindOfMatcher.new(Numeric) + matcher.matches?(4.0) + matcher.negative_failure_message.should == [ + "Expected 4.0 (Float)", "not to be kind of Numeric"] + end +end diff --git a/spec/mspec/spec/matchers/be_nan_spec.rb b/spec/mspec/spec/matchers/be_nan_spec.rb new file mode 100644 index 0000000000..2062763a92 --- /dev/null +++ b/spec/mspec/spec/matchers/be_nan_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' +require 'mspec/expectations/expectations' +require 'mspec/guards' +require 'mspec/helpers' +require 'mspec/matchers' + +describe BeNaNMatcher do + it "matches when actual is NaN" do + BeNaNMatcher.new.matches?(nan_value).should == true + end + + it "does not match when actual is not NaN" do + BeNaNMatcher.new.matches?(1.0).should == false + BeNaNMatcher.new.matches?(0).should == false + end + + it "provides a useful failure message" do + matcher = BeNaNMatcher.new + matcher.matches?(0) + matcher.failure_message.should == ["Expected 0", "to be NaN"] + end + + it "provides a useful negative failure message" do + matcher = BeNaNMatcher.new + matcher.matches?(nan_value) + matcher.negative_failure_message.should == ["Expected NaN", "not to be NaN"] + end +end diff --git a/spec/mspec/spec/matchers/be_nil_spec.rb b/spec/mspec/spec/matchers/be_nil_spec.rb new file mode 100644 index 0000000000..6551feb5de --- /dev/null +++ b/spec/mspec/spec/matchers/be_nil_spec.rb @@ -0,0 +1,27 @@ +require 'spec_helper' +require 'mspec/expectations/expectations' +require 'mspec/matchers' + +describe BeNilMatcher do + it "matches when actual is nil" do + BeNilMatcher.new.matches?(nil).should == true + end + + it "does not match when actual is not nil" do + BeNilMatcher.new.matches?("").should == false + BeNilMatcher.new.matches?(false).should == false + BeNilMatcher.new.matches?(0).should == false + end + + it "provides a useful failure message" do + matcher = BeNilMatcher.new + matcher.matches?("some string") + matcher.failure_message.should == ["Expected \"some string\"", "to be nil"] + end + + it "provides a useful negative failure message" do + matcher = BeNilMatcher.new + matcher.matches?(nil) + matcher.negative_failure_message.should == ["Expected nil", "not to be nil"] + end +end diff --git a/spec/mspec/spec/matchers/be_true_or_false_spec.rb b/spec/mspec/spec/matchers/be_true_or_false_spec.rb new file mode 100644 index 0000000000..3edffcb1b1 --- /dev/null +++ b/spec/mspec/spec/matchers/be_true_or_false_spec.rb @@ -0,0 +1,19 @@ +require 'spec_helper' +require 'mspec/expectations/expectations' +require 'mspec/matchers' + +describe BeTrueOrFalseMatcher do + it "matches when actual is true" do + BeTrueOrFalseMatcher.new.matches?(true).should == true + end + + it "matches when actual is false" do + BeTrueOrFalseMatcher.new.matches?(false).should == true + end + + it "provides a useful failure message" do + matcher = BeTrueOrFalseMatcher.new + matcher.matches?("some string") + matcher.failure_message.should == ["Expected \"some string\"", "to be true or false"] + end +end diff --git a/spec/mspec/spec/matchers/be_true_spec.rb b/spec/mspec/spec/matchers/be_true_spec.rb new file mode 100644 index 0000000000..90c89b3911 --- /dev/null +++ b/spec/mspec/spec/matchers/be_true_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' +require 'mspec/expectations/expectations' +require 'mspec/matchers' + +describe BeTrueMatcher do + it "matches when actual is true" do + BeTrueMatcher.new.matches?(true).should == true + end + + it "does not match when actual is not true" do + BeTrueMatcher.new.matches?("").should == false + BeTrueMatcher.new.matches?(false).should == false + BeTrueMatcher.new.matches?(nil).should == false + BeTrueMatcher.new.matches?(0).should == false + end + + it "provides a useful failure message" do + matcher = BeTrueMatcher.new + matcher.matches?("some string") + matcher.failure_message.should == ["Expected \"some string\"", "to be true"] + end + + it "provides a useful negative failure message" do + matcher = BeTrueMatcher.new + matcher.matches?(true) + matcher.negative_failure_message.should == ["Expected true", "not to be true"] + end +end diff --git a/spec/mspec/spec/matchers/block_caller_spec.rb b/spec/mspec/spec/matchers/block_caller_spec.rb new file mode 100644 index 0000000000..d6793b9779 --- /dev/null +++ b/spec/mspec/spec/matchers/block_caller_spec.rb @@ -0,0 +1,13 @@ +require 'spec_helper' +require 'mspec/expectations/expectations' +require 'mspec/matchers' + +describe BlockingMatcher do + it 'matches when a Proc blocks the caller' do + BlockingMatcher.new.matches?(proc { sleep }).should == true + end + + it 'does not match when a Proc does not block the caller' do + BlockingMatcher.new.matches?(proc { 1 }).should == false + end +end diff --git a/spec/mspec/spec/matchers/complain_spec.rb b/spec/mspec/spec/matchers/complain_spec.rb new file mode 100644 index 0000000000..709b57be6c --- /dev/null +++ b/spec/mspec/spec/matchers/complain_spec.rb @@ -0,0 +1,52 @@ +require 'spec_helper' +require 'mspec/expectations/expectations' +require 'mspec/matchers' + +describe ComplainMatcher do + it "matches when executing the proc results in output to $stderr" do + proc = lambda { warn "I'm gonna tell yo mama" } + ComplainMatcher.new(nil).matches?(proc).should == true + end + + it "maches when executing the proc results in the expected output to $stderr" do + proc = lambda { warn "Que haces?" } + ComplainMatcher.new("Que haces?\n").matches?(proc).should == true + ComplainMatcher.new("Que pasa?\n").matches?(proc).should == false + ComplainMatcher.new(/Que/).matches?(proc).should == true + ComplainMatcher.new(/Quoi/).matches?(proc).should == false + end + + it "does not match when there is no output to $stderr" do + ComplainMatcher.new(nil).matches?(lambda {}).should == false + end + + it "provides a useful failure message" do + matcher = ComplainMatcher.new(nil) + matcher.matches?(lambda { }) + matcher.failure_message.should == ["Expected a warning", "but received none"] + matcher = ComplainMatcher.new("listen here") + matcher.matches?(lambda { warn "look out" }) + matcher.failure_message.should == + ["Expected warning: \"listen here\"", "but got: \"look out\""] + matcher = ComplainMatcher.new(/talk/) + matcher.matches?(lambda { warn "listen up" }) + matcher.failure_message.should == + ["Expected warning to match: /talk/", "but got: \"listen up\""] + end + + it "provides a useful negative failure message" do + proc = lambda { warn "ouch" } + matcher = ComplainMatcher.new(nil) + matcher.matches?(proc) + matcher.negative_failure_message.should == + ["Unexpected warning: ", "\"ouch\""] + matcher = ComplainMatcher.new("ouchy") + matcher.matches?(proc) + matcher.negative_failure_message.should == + ["Expected warning: \"ouchy\"", "but got: \"ouch\""] + matcher = ComplainMatcher.new(/ou/) + matcher.matches?(proc) + matcher.negative_failure_message.should == + ["Expected warning not to match: /ou/", "but got: \"ouch\""] + end +end diff --git a/spec/mspec/spec/matchers/eql_spec.rb b/spec/mspec/spec/matchers/eql_spec.rb new file mode 100644 index 0000000000..711ebdb679 --- /dev/null +++ b/spec/mspec/spec/matchers/eql_spec.rb @@ -0,0 +1,33 @@ +require 'spec_helper' +require 'mspec/expectations/expectations' +require 'mspec/matchers' + +describe EqlMatcher do + it "matches when actual is eql? to expected" do + EqlMatcher.new(1).matches?(1).should == true + EqlMatcher.new(1.5).matches?(1.5).should == true + EqlMatcher.new("red").matches?("red").should == true + EqlMatcher.new(:blue).matches?(:blue).should == true + EqlMatcher.new(Object).matches?(Object).should == true + + o = Object.new + EqlMatcher.new(o).matches?(o).should == true + end + + it "does not match when actual is not eql? to expected" do + EqlMatcher.new(1).matches?(1.0).should == false + EqlMatcher.new(Hash).matches?(Object).should == false + end + + it "provides a useful failure message" do + matcher = EqlMatcher.new("red") + matcher.matches?("red") + matcher.failure_message.should == ["Expected \"red\"\n", "to have same value and type as \"red\"\n"] + end + + it "provides a useful negative failure message" do + matcher = EqlMatcher.new(1) + matcher.matches?(1.0) + matcher.negative_failure_message.should == ["Expected 1.0\n", "not to have same value or type as 1\n"] + end +end diff --git a/spec/mspec/spec/matchers/equal_element_spec.rb b/spec/mspec/spec/matchers/equal_element_spec.rb new file mode 100644 index 0000000000..45b8390364 --- /dev/null +++ b/spec/mspec/spec/matchers/equal_element_spec.rb @@ -0,0 +1,75 @@ +require 'spec_helper' +require 'mspec/expectations/expectations' +require 'mspec/matchers' + +describe EqualElementMatcher do + it "matches if it finds an element with the passed name, no matter what attributes/content" do + EqualElementMatcher.new("A").matches?('').should be_true + EqualElementMatcher.new("A").matches?('').should be_true + EqualElementMatcher.new("A").matches?('').should be_true + + EqualElementMatcher.new("BASE").matches?('').should be_false + EqualElementMatcher.new("BASE").matches?('').should be_false + EqualElementMatcher.new("BASE").matches?('').should be_false + EqualElementMatcher.new("BASE").matches?('').should be_false + EqualElementMatcher.new("BASE").matches?('').should be_false + end + + it "matches if it finds an element with the passed name and the passed attributes" do + EqualElementMatcher.new("A", {}).matches?('').should be_true + EqualElementMatcher.new("A", nil).matches?('').should be_true + EqualElementMatcher.new("A", "HREF" => "http://example.com").matches?('').should be_true + + EqualElementMatcher.new("A", {}).matches?('').should be_false + EqualElementMatcher.new("A", "HREF" => "http://example.com").matches?('').should be_false + EqualElementMatcher.new("A", "HREF" => "http://example.com").matches?('').should be_false + EqualElementMatcher.new("A", "HREF" => "http://example.com").matches?('').should be_false + end + + it "matches if it finds an element with the passed name, the passed attributes and the passed content" do + EqualElementMatcher.new("A", {}, "").matches?('').should be_true + EqualElementMatcher.new("A", {"HREF" => "http://example.com"}, "Example").matches?('Example').should be_true + + EqualElementMatcher.new("A", {}, "Test").matches?('').should be_false + EqualElementMatcher.new("A", {"HREF" => "http://example.com"}, "Example").matches?('').should be_false + EqualElementMatcher.new("A", {"HREF" => "http://example.com"}, "Example").matches?('Test').should be_false + end + + it "can match unclosed elements" do + EqualElementMatcher.new("BASE", nil, nil, :not_closed => true).matches?('').should be_true + EqualElementMatcher.new("BASE", {"HREF" => "http://example.com"}, nil, :not_closed => true).matches?('').should be_true + EqualElementMatcher.new("BASE", {"HREF" => "http://example.com"}, "Example", :not_closed => true).matches?('Example').should be_true + + EqualElementMatcher.new("BASE", {}, nil, :not_closed => true).matches?('').should be_false + EqualElementMatcher.new("BASE", {"HREF" => "http://example.com"}, "", :not_closed => true).matches?('Example').should be_false + EqualElementMatcher.new("BASE", {"HREF" => "http://example.com"}, "Test", :not_closed => true).matches?('Example').should be_false + end + + it "provides a useful failure message" do + equal_element = EqualElementMatcher.new("A", {}, "Test") + equal_element.matches?('').should be_false + equal_element.failure_message.should == [%{Expected ""\n}, %{to be a 'A' element with no attributes and "Test" as content}] + + equal_element = EqualElementMatcher.new("A", {}, "") + equal_element.matches?('Test').should be_false + equal_element.failure_message.should == [%{Expected "Test"\n}, %{to be a 'A' element with no attributes and no content}] + + equal_element = EqualElementMatcher.new("A", "HREF" => "http://www.example.com") + equal_element.matches?('Test').should be_false + equal_element.failure_message.should == [%{Expected "Test"\n}, %{to be a 'A' element with HREF="http://www.example.com" and any content}] + end + + it "provides a useful negative failure message" do + equal_element = EqualElementMatcher.new("A", {}, "Test") + equal_element.matches?('').should be_false + equal_element.negative_failure_message.should == [%{Expected ""\n}, %{not to be a 'A' element with no attributes and "Test" as content}] + + equal_element = EqualElementMatcher.new("A", {}, "") + equal_element.matches?('Test').should be_false + equal_element.negative_failure_message.should == [%{Expected "Test"\n}, %{not to be a 'A' element with no attributes and no content}] + + equal_element = EqualElementMatcher.new("A", "HREF" => "http://www.example.com") + equal_element.matches?('Test').should be_false + equal_element.negative_failure_message.should == [%{Expected "Test"\n}, %{not to be a 'A' element with HREF="http://www.example.com" and any content}] + end +end diff --git a/spec/mspec/spec/matchers/equal_spec.rb b/spec/mspec/spec/matchers/equal_spec.rb new file mode 100644 index 0000000000..ca7bf83fdd --- /dev/null +++ b/spec/mspec/spec/matchers/equal_spec.rb @@ -0,0 +1,32 @@ +require 'spec_helper' +require 'mspec/expectations/expectations' +require 'mspec/matchers' + +describe EqualMatcher do + it "matches when actual is equal? to expected" do + EqualMatcher.new(1).matches?(1).should == true + EqualMatcher.new(:blue).matches?(:blue).should == true + EqualMatcher.new(Object).matches?(Object).should == true + + o = Object.new + EqualMatcher.new(o).matches?(o).should == true + end + + it "does not match when actual is not a equal? to expected" do + EqualMatcher.new(1).matches?(1.0).should == false + EqualMatcher.new("blue").matches?("blue").should == false + EqualMatcher.new(Hash).matches?(Object).should == false + end + + it "provides a useful failure message" do + matcher = EqualMatcher.new("red") + matcher.matches?("red") + matcher.failure_message.should == ["Expected \"red\"\n", "to be identical to \"red\"\n"] + end + + it "provides a useful negative failure message" do + matcher = EqualMatcher.new(1) + matcher.matches?(1) + matcher.negative_failure_message.should == ["Expected 1\n", "not to be identical to 1\n"] + end +end diff --git a/spec/mspec/spec/matchers/have_class_variable_spec.rb b/spec/mspec/spec/matchers/have_class_variable_spec.rb new file mode 100644 index 0000000000..e440050056 --- /dev/null +++ b/spec/mspec/spec/matchers/have_class_variable_spec.rb @@ -0,0 +1,62 @@ +require 'spec_helper' +require 'mspec/expectations/expectations' +require 'mspec/matchers' + +class IVarModMock; end + +shared_examples_for "have_class_variable, on all Ruby versions" do + after :all do + Object.const_set :RUBY_VERSION, @ruby_version + end + + it "matches when mod has the class variable, given as string" do + matcher = HaveClassVariableMatcher.new('@foo') + matcher.matches?(IVarModMock).should be_true + end + + it "matches when mod has the class variable, given as symbol" do + matcher = HaveClassVariableMatcher.new(:@foo) + matcher.matches?(IVarModMock).should be_true + end + + it "does not match when mod hasn't got the class variable, given as string" do + matcher = HaveClassVariableMatcher.new('@bar') + matcher.matches?(IVarModMock).should be_false + end + + it "does not match when mod hasn't got the class variable, given as symbol" do + matcher = HaveClassVariableMatcher.new(:@bar) + matcher.matches?(IVarModMock).should be_false + end + + it "provides a failure message for #should" do + matcher = HaveClassVariableMatcher.new(:@bar) + matcher.matches?(IVarModMock) + matcher.failure_message.should == [ + "Expected IVarModMock to have class variable '@bar'", + "but it does not" + ] + end + + it "provides a failure messoge for #should_not" do + matcher = HaveClassVariableMatcher.new(:@bar) + matcher.matches?(IVarModMock) + matcher.negative_failure_message.should == [ + "Expected IVarModMock NOT to have class variable '@bar'", + "but it does" + ] + end +end + +describe HaveClassVariableMatcher, "on RUBY_VERSION >= 1.9" do + before :all do + @ruby_version = Object.const_get :RUBY_VERSION + Object.const_set :RUBY_VERSION, '1.9.0' + + def IVarModMock.class_variables + [:@foo] + end + end + + it_should_behave_like "have_class_variable, on all Ruby versions" +end diff --git a/spec/mspec/spec/matchers/have_constant_spec.rb b/spec/mspec/spec/matchers/have_constant_spec.rb new file mode 100644 index 0000000000..20c5f161d4 --- /dev/null +++ b/spec/mspec/spec/matchers/have_constant_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper' +require 'mspec/expectations/expectations' +require 'mspec/matchers' + +class HCMSpecs + X = :x +end + +describe HaveConstantMatcher do + it "matches when mod has the constant" do + matcher = HaveConstantMatcher.new :X + matcher.matches?(HCMSpecs).should be_true + end + + it "does not match when mod does not have the constant" do + matcher = HaveConstantMatcher.new :A + matcher.matches?(HCMSpecs).should be_false + end + + it "provides a failure message for #should" do + matcher = HaveConstantMatcher.new :A + matcher.matches?(HCMSpecs) + matcher.failure_message.should == [ + "Expected HCMSpecs to have constant 'A'", + "but it does not" + ] + end + + it "provides a failure messoge for #should_not" do + matcher = HaveConstantMatcher.new :X + matcher.matches?(HCMSpecs) + matcher.negative_failure_message.should == [ + "Expected HCMSpecs NOT to have constant 'X'", + "but it does" + ] + end +end diff --git a/spec/mspec/spec/matchers/have_instance_method_spec.rb b/spec/mspec/spec/matchers/have_instance_method_spec.rb new file mode 100644 index 0000000000..738f5f875d --- /dev/null +++ b/spec/mspec/spec/matchers/have_instance_method_spec.rb @@ -0,0 +1,53 @@ +require 'spec_helper' +require 'mspec/expectations/expectations' +require 'mspec/matchers' + +class HIMMSpecs + def instance_method + end + + class Subclass < HIMMSpecs + def instance_sub_method + end + end +end + +describe HaveInstanceMethodMatcher do + it "inherits from MethodMatcher" do + HaveInstanceMethodMatcher.new(:m).should be_kind_of(MethodMatcher) + end + + it "matches when mod has the instance method" do + matcher = HaveInstanceMethodMatcher.new :instance_method + matcher.matches?(HIMMSpecs).should be_true + matcher.matches?(HIMMSpecs::Subclass).should be_true + end + + it "does not match when mod does not have the instance method" do + matcher = HaveInstanceMethodMatcher.new :another_method + matcher.matches?(HIMMSpecs).should be_false + end + + it "does not match if the method is in a superclass and include_super is false" do + matcher = HaveInstanceMethodMatcher.new :instance_method, false + matcher.matches?(HIMMSpecs::Subclass).should be_false + end + + it "provides a failure message for #should" do + matcher = HaveInstanceMethodMatcher.new :some_method + matcher.matches?(HIMMSpecs) + matcher.failure_message.should == [ + "Expected HIMMSpecs to have instance method 'some_method'", + "but it does not" + ] + end + + it "provides a failure messoge for #should_not" do + matcher = HaveInstanceMethodMatcher.new :some_method + matcher.matches?(HIMMSpecs) + matcher.negative_failure_message.should == [ + "Expected HIMMSpecs NOT to have instance method 'some_method'", + "but it does" + ] + end +end diff --git a/spec/mspec/spec/matchers/have_instance_variable_spec.rb b/spec/mspec/spec/matchers/have_instance_variable_spec.rb new file mode 100644 index 0000000000..ababb38bc7 --- /dev/null +++ b/spec/mspec/spec/matchers/have_instance_variable_spec.rb @@ -0,0 +1,61 @@ +require 'spec_helper' +require 'mspec/expectations/expectations' +require 'mspec/matchers' + +shared_examples_for "have_instance_variable, on all Ruby versions" do + after :all do + Object.const_set :RUBY_VERSION, @ruby_version + end + + it "matches when object has the instance variable, given as string" do + matcher = HaveInstanceVariableMatcher.new('@foo') + matcher.matches?(@object).should be_true + end + + it "matches when object has the instance variable, given as symbol" do + matcher = HaveInstanceVariableMatcher.new(:@foo) + matcher.matches?(@object).should be_true + end + + it "does not match when object hasn't got the instance variable, given as string" do + matcher = HaveInstanceVariableMatcher.new('@bar') + matcher.matches?(@object).should be_false + end + + it "does not match when object hasn't got the instance variable, given as symbol" do + matcher = HaveInstanceVariableMatcher.new(:@bar) + matcher.matches?(@object).should be_false + end + + it "provides a failure message for #should" do + matcher = HaveInstanceVariableMatcher.new(:@bar) + matcher.matches?(@object) + matcher.failure_message.should == [ + "Expected #{@object.inspect} to have instance variable '@bar'", + "but it does not" + ] + end + + it "provides a failure messoge for #should_not" do + matcher = HaveInstanceVariableMatcher.new(:@bar) + matcher.matches?(@object) + matcher.negative_failure_message.should == [ + "Expected #{@object.inspect} NOT to have instance variable '@bar'", + "but it does" + ] + end +end + +describe HaveInstanceVariableMatcher, "on RUBY_VERSION >= 1.9" do + before :all do + @ruby_version = Object.const_get :RUBY_VERSION + Object.const_set :RUBY_VERSION, '1.9.0' + + @object = Object.new + def @object.instance_variables + [:@foo] + end + end + + it_should_behave_like "have_instance_variable, on all Ruby versions" +end diff --git a/spec/mspec/spec/matchers/have_method_spec.rb b/spec/mspec/spec/matchers/have_method_spec.rb new file mode 100644 index 0000000000..41bd485119 --- /dev/null +++ b/spec/mspec/spec/matchers/have_method_spec.rb @@ -0,0 +1,55 @@ +require 'spec_helper' +require 'mspec/expectations/expectations' +require 'mspec/matchers' + +class HMMSpecs + def instance_method + end + + class Subclass < HMMSpecs + def instance_sub_method + end + end +end + +describe HaveMethodMatcher do + it "inherits from MethodMatcher" do + HaveMethodMatcher.new(:m).should be_kind_of(MethodMatcher) + end + + it "matches when mod has the method" do + matcher = HaveMethodMatcher.new :instance_method + matcher.matches?(HMMSpecs).should be_true + matcher.matches?(HMMSpecs.new).should be_true + matcher.matches?(HMMSpecs::Subclass).should be_true + matcher.matches?(HMMSpecs::Subclass.new).should be_true + end + + it "does not match when mod does not have the method" do + matcher = HaveMethodMatcher.new :another_method + matcher.matches?(HMMSpecs).should be_false + end + + it "does not match if the method is in a superclass and include_super is false" do + matcher = HaveMethodMatcher.new :instance_method, false + matcher.matches?(HMMSpecs::Subclass).should be_false + end + + it "provides a failure message for #should" do + matcher = HaveMethodMatcher.new :some_method + matcher.matches?(HMMSpecs) + matcher.failure_message.should == [ + "Expected HMMSpecs to have method 'some_method'", + "but it does not" + ] + end + + it "provides a failure messoge for #should_not" do + matcher = HaveMethodMatcher.new :some_method + matcher.matches?(HMMSpecs) + matcher.negative_failure_message.should == [ + "Expected HMMSpecs NOT to have method 'some_method'", + "but it does" + ] + end +end diff --git a/spec/mspec/spec/matchers/have_private_instance_method_spec.rb b/spec/mspec/spec/matchers/have_private_instance_method_spec.rb new file mode 100644 index 0000000000..827c6b6034 --- /dev/null +++ b/spec/mspec/spec/matchers/have_private_instance_method_spec.rb @@ -0,0 +1,57 @@ +require 'spec_helper' +require 'mspec/expectations/expectations' +require 'mspec/matchers' + +class HPIMMSpecs + private + + def private_method + end + + class Subclass < HPIMMSpecs + private + + def private_sub_method + end + end +end + +describe HavePrivateInstanceMethodMatcher do + it "inherits from MethodMatcher" do + HavePrivateInstanceMethodMatcher.new(:m).should be_kind_of(MethodMatcher) + end + + it "matches when mod has the private instance method" do + matcher = HavePrivateInstanceMethodMatcher.new :private_method + matcher.matches?(HPIMMSpecs).should be_true + matcher.matches?(HPIMMSpecs::Subclass).should be_true + end + + it "does not match when mod does not have the private instance method" do + matcher = HavePrivateInstanceMethodMatcher.new :another_method + matcher.matches?(HPIMMSpecs).should be_false + end + + it "does not match if the method is in a superclass and include_super is false" do + matcher = HavePrivateInstanceMethodMatcher.new :private_method, false + matcher.matches?(HPIMMSpecs::Subclass).should be_false + end + + it "provides a failure message for #should" do + matcher = HavePrivateInstanceMethodMatcher.new :some_method + matcher.matches?(HPIMMSpecs) + matcher.failure_message.should == [ + "Expected HPIMMSpecs to have private instance method 'some_method'", + "but it does not" + ] + end + + it "provides a failure message for #should_not" do + matcher = HavePrivateInstanceMethodMatcher.new :some_method + matcher.matches?(HPIMMSpecs) + matcher.negative_failure_message.should == [ + "Expected HPIMMSpecs NOT to have private instance method 'some_method'", + "but it does" + ] + end +end diff --git a/spec/mspec/spec/matchers/have_private_method_spec.rb b/spec/mspec/spec/matchers/have_private_method_spec.rb new file mode 100644 index 0000000000..e63a9a3c2f --- /dev/null +++ b/spec/mspec/spec/matchers/have_private_method_spec.rb @@ -0,0 +1,44 @@ +require 'spec_helper' +require 'mspec/expectations/expectations' +require 'mspec/matchers' + +class HPMMSpecs + def self.private_method + end + + private_class_method :private_method +end + +describe HavePrivateMethodMatcher do + it "inherits from MethodMatcher" do + HavePrivateMethodMatcher.new(:m).should be_kind_of(MethodMatcher) + end + + it "matches when mod has the private method" do + matcher = HavePrivateMethodMatcher.new :private_method + matcher.matches?(HPMMSpecs).should be_true + end + + it "does not match when mod does not have the private method" do + matcher = HavePrivateMethodMatcher.new :another_method + matcher.matches?(HPMMSpecs).should be_false + end + + it "provides a failure message for #should" do + matcher = HavePrivateMethodMatcher.new :some_method + matcher.matches?(HPMMSpecs) + matcher.failure_message.should == [ + "Expected HPMMSpecs to have private method 'some_method'", + "but it does not" + ] + end + + it "provides a failure message for #should_not" do + matcher = HavePrivateMethodMatcher.new :private_method + matcher.matches?(HPMMSpecs) + matcher.negative_failure_message.should == [ + "Expected HPMMSpecs NOT to have private method 'private_method'", + "but it does" + ] + end +end diff --git a/spec/mspec/spec/matchers/have_protected_instance_method_spec.rb b/spec/mspec/spec/matchers/have_protected_instance_method_spec.rb new file mode 100644 index 0000000000..460d0368fb --- /dev/null +++ b/spec/mspec/spec/matchers/have_protected_instance_method_spec.rb @@ -0,0 +1,57 @@ +require 'spec_helper' +require 'mspec/expectations/expectations' +require 'mspec/matchers' + +class HPIMMSpecs + protected + + def protected_method + end + + class Subclass < HPIMMSpecs + protected + + def protected_sub_method + end + end +end + +describe HaveProtectedInstanceMethodMatcher do + it "inherits from MethodMatcher" do + HaveProtectedInstanceMethodMatcher.new(:m).should be_kind_of(MethodMatcher) + end + + it "matches when mod has the protected instance method" do + matcher = HaveProtectedInstanceMethodMatcher.new :protected_method + matcher.matches?(HPIMMSpecs).should be_true + matcher.matches?(HPIMMSpecs::Subclass).should be_true + end + + it "does not match when mod does not have the protected instance method" do + matcher = HaveProtectedInstanceMethodMatcher.new :another_method + matcher.matches?(HPIMMSpecs).should be_false + end + + it "does not match if the method is in a superclass and include_super is false" do + matcher = HaveProtectedInstanceMethodMatcher.new :protected_method, false + matcher.matches?(HPIMMSpecs::Subclass).should be_false + end + + it "provides a failure message for #should" do + matcher = HaveProtectedInstanceMethodMatcher.new :some_method + matcher.matches?(HPIMMSpecs) + matcher.failure_message.should == [ + "Expected HPIMMSpecs to have protected instance method 'some_method'", + "but it does not" + ] + end + + it "provides a failure messoge for #should_not" do + matcher = HaveProtectedInstanceMethodMatcher.new :some_method + matcher.matches?(HPIMMSpecs) + matcher.negative_failure_message.should == [ + "Expected HPIMMSpecs NOT to have protected instance method 'some_method'", + "but it does" + ] + end +end diff --git a/spec/mspec/spec/matchers/have_public_instance_method_spec.rb b/spec/mspec/spec/matchers/have_public_instance_method_spec.rb new file mode 100644 index 0000000000..bff1046f04 --- /dev/null +++ b/spec/mspec/spec/matchers/have_public_instance_method_spec.rb @@ -0,0 +1,53 @@ +require 'spec_helper' +require 'mspec/expectations/expectations' +require 'mspec/matchers' + +class HPIMMSpecs + def public_method + end + + class Subclass < HPIMMSpecs + def public_sub_method + end + end +end + +describe HavePublicInstanceMethodMatcher do + it "inherits from MethodMatcher" do + HavePublicInstanceMethodMatcher.new(:m).should be_kind_of(MethodMatcher) + end + + it "matches when mod has the public instance method" do + matcher = HavePublicInstanceMethodMatcher.new :public_method + matcher.matches?(HPIMMSpecs).should be_true + matcher.matches?(HPIMMSpecs::Subclass).should be_true + end + + it "does not match when mod does not have the public instance method" do + matcher = HavePublicInstanceMethodMatcher.new :another_method + matcher.matches?(HPIMMSpecs).should be_false + end + + it "does not match if the method is in a superclass and include_super is false" do + matcher = HavePublicInstanceMethodMatcher.new :public_method, false + matcher.matches?(HPIMMSpecs::Subclass).should be_false + end + + it "provides a failure message for #should" do + matcher = HavePublicInstanceMethodMatcher.new :some_method + matcher.matches?(HPIMMSpecs) + matcher.failure_message.should == [ + "Expected HPIMMSpecs to have public instance method 'some_method'", + "but it does not" + ] + end + + it "provides a failure messoge for #should_not" do + matcher = HavePublicInstanceMethodMatcher.new :some_method + matcher.matches?(HPIMMSpecs) + matcher.negative_failure_message.should == [ + "Expected HPIMMSpecs NOT to have public instance method 'some_method'", + "but it does" + ] + end +end diff --git a/spec/mspec/spec/matchers/have_singleton_method_spec.rb b/spec/mspec/spec/matchers/have_singleton_method_spec.rb new file mode 100644 index 0000000000..57c37e01d9 --- /dev/null +++ b/spec/mspec/spec/matchers/have_singleton_method_spec.rb @@ -0,0 +1,45 @@ +require 'spec_helper' +require 'mspec/expectations/expectations' +require 'mspec/matchers' + +class HSMMSpecs + def self.singleton_method + end +end + +describe HaveSingletonMethodMatcher do + it "inherits from MethodMatcher" do + HaveSingletonMethodMatcher.new(:m).should be_kind_of(MethodMatcher) + end + + it "matches when the class has a singleton method" do + matcher = HaveSingletonMethodMatcher.new :singleton_method + matcher.matches?(HSMMSpecs).should be_true + end + + it "matches when the object has a singleton method" do + obj = double("HSMMSpecs") + def obj.singleton_method; end + + matcher = HaveSingletonMethodMatcher.new :singleton_method + matcher.matches?(obj).should be_true + end + + it "provides a failure message for #should" do + matcher = HaveSingletonMethodMatcher.new :some_method + matcher.matches?(HSMMSpecs) + matcher.failure_message.should == [ + "Expected HSMMSpecs to have singleton method 'some_method'", + "but it does not" + ] + end + + it "provides a failure message for #should_not" do + matcher = HaveSingletonMethodMatcher.new :singleton_method + matcher.matches?(HSMMSpecs) + matcher.negative_failure_message.should == [ + "Expected HSMMSpecs NOT to have singleton method 'singleton_method'", + "but it does" + ] + end +end diff --git a/spec/mspec/spec/matchers/include_spec.rb b/spec/mspec/spec/matchers/include_spec.rb new file mode 100644 index 0000000000..f045c5e0cb --- /dev/null +++ b/spec/mspec/spec/matchers/include_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper' +require 'mspec/expectations/expectations' +require 'mspec/matchers' + +describe IncludeMatcher do + it "matches when actual includes expected" do + IncludeMatcher.new(2).matches?([1,2,3]).should == true + IncludeMatcher.new("b").matches?("abc").should == true + end + + it "does not match when actual does not include expected" do + IncludeMatcher.new(4).matches?([1,2,3]).should == false + IncludeMatcher.new("d").matches?("abc").should == false + end + + it "matches when actual includes all expected" do + IncludeMatcher.new(3, 2, 1).matches?([1,2,3]).should == true + IncludeMatcher.new("a", "b", "c").matches?("abc").should == true + end + + it "does not match when actual does not include all expected" do + IncludeMatcher.new(3, 2, 4).matches?([1,2,3]).should == false + IncludeMatcher.new("a", "b", "c", "d").matches?("abc").should == false + end + + it "provides a useful failure message" do + matcher = IncludeMatcher.new(5, 2) + matcher.matches?([1,2,3]) + matcher.failure_message.should == ["Expected [1, 2, 3]", "to include 5"] + end + + it "provides a useful negative failure message" do + matcher = IncludeMatcher.new(1, 2, 3) + matcher.matches?([1,2,3]) + matcher.negative_failure_message.should == ["Expected [1, 2, 3]", "not to include 3"] + end +end diff --git a/spec/mspec/spec/matchers/infinity_spec.rb b/spec/mspec/spec/matchers/infinity_spec.rb new file mode 100644 index 0000000000..6eb8ac2940 --- /dev/null +++ b/spec/mspec/spec/matchers/infinity_spec.rb @@ -0,0 +1,34 @@ +require 'spec_helper' +require 'mspec/expectations/expectations' +require 'mspec/guards' +require 'mspec/helpers' +require 'mspec/matchers' + +describe InfinityMatcher do + it "matches when actual is infinite and has the correct sign" do + InfinityMatcher.new(1).matches?(infinity_value).should == true + InfinityMatcher.new(-1).matches?(-infinity_value).should == true + end + + it "does not match when actual is not infinite" do + InfinityMatcher.new(1).matches?(1.0).should == false + InfinityMatcher.new(-1).matches?(-1.0).should == false + end + + it "does not match when actual is infinite but has the incorrect sign" do + InfinityMatcher.new(1).matches?(-infinity_value).should == false + InfinityMatcher.new(-1).matches?(infinity_value).should == false + end + + it "provides a useful failure message" do + matcher = InfinityMatcher.new(-1) + matcher.matches?(0) + matcher.failure_message.should == ["Expected 0", "to be -Infinity"] + end + + it "provides a useful negative failure message" do + matcher = InfinityMatcher.new(1) + matcher.matches?(infinity_value) + matcher.negative_failure_message.should == ["Expected Infinity", "not to be Infinity"] + end +end diff --git a/spec/mspec/spec/matchers/match_yaml_spec.rb b/spec/mspec/spec/matchers/match_yaml_spec.rb new file mode 100644 index 0000000000..4f16aee0ec --- /dev/null +++ b/spec/mspec/spec/matchers/match_yaml_spec.rb @@ -0,0 +1,39 @@ +require 'spec_helper' +require 'mspec/expectations/expectations' +require 'mspec/matchers' + +describe MatchYAMLMatcher do + before :each do + @matcher = MatchYAMLMatcher.new("--- \nfoo: bar\n") + end + + it "compares YAML documents and matches if they're equivalent" do + @matcher.matches?("--- \nfoo: bar\n").should == true + end + + it "compares YAML documents and does not match if they're not equivalent" do + @matcher.matches?("--- \nbar: foo\n").should == false + @matcher.matches?("--- \nfoo: \nbar\n").should == false + end + + it "also receives objects that respond_to to_yaml" do + matcher = MatchYAMLMatcher.new("some string") + matcher.matches?("some string").should == true + + matcher = MatchYAMLMatcher.new(['a', 'b']) + matcher.matches?("--- \n- a\n- b\n").should == true + + matcher = MatchYAMLMatcher.new("foo" => "bar") + matcher.matches?("--- \nfoo: bar\n").should == true + end + + it "matches documents with trailing whitespace" do + @matcher.matches?("--- \nfoo: bar \n").should == true + @matcher.matches?("--- \nfoo: bar \n").should == true + end + + it "fails with a descriptive error message" do + @matcher.matches?("foo").should == false + @matcher.failure_message.should == ["Expected \"foo\"", " to match \"--- \\nfoo: bar\\n\""] + end +end diff --git a/spec/mspec/spec/matchers/output_spec.rb b/spec/mspec/spec/matchers/output_spec.rb new file mode 100644 index 0000000000..264da3b569 --- /dev/null +++ b/spec/mspec/spec/matchers/output_spec.rb @@ -0,0 +1,74 @@ +require 'spec_helper' +require 'mspec/expectations/expectations' +require 'mspec/matchers' + +describe OutputMatcher do + it "matches when executing the proc results in the expected output to $stdout" do + proc = Proc.new { puts "bang!" } + OutputMatcher.new("bang!\n", nil).matches?(proc).should == true + OutputMatcher.new("pop", nil).matches?(proc).should == false + OutputMatcher.new(/bang/, nil).matches?(proc).should == true + OutputMatcher.new(/po/, nil).matches?(proc).should == false + end + + it "matches when executing the proc results in the expected output to $stderr" do + proc = Proc.new { $stderr.write "boom!" } + OutputMatcher.new(nil, "boom!").matches?(proc).should == true + OutputMatcher.new(nil, "fizzle").matches?(proc).should == false + OutputMatcher.new(nil, /boom/).matches?(proc).should == true + OutputMatcher.new(nil, /fizzl/).matches?(proc).should == false + end + + it "provides a useful failure message" do + proc = Proc.new { print "unexpected"; $stderr.print "unerror" } + matcher = OutputMatcher.new("expected", "error") + matcher.matches?(proc) + matcher.failure_message.should == + ["Expected:\n $stdout: \"expected\"\n $stderr: \"error\"\n", + " got:\n $stdout: \"unexpected\"\n $stderr: \"unerror\"\n"] + matcher = OutputMatcher.new("expected", nil) + matcher.matches?(proc) + matcher.failure_message.should == + ["Expected:\n $stdout: \"expected\"\n", + " got:\n $stdout: \"unexpected\"\n"] + matcher = OutputMatcher.new(nil, "error") + matcher.matches?(proc) + matcher.failure_message.should == + ["Expected:\n $stderr: \"error\"\n", + " got:\n $stderr: \"unerror\"\n"] + matcher = OutputMatcher.new(/base/, nil) + matcher.matches?(proc) + matcher.failure_message.should == + ["Expected:\n $stdout: /base/\n", + " got:\n $stdout: \"unexpected\"\n"] + matcher = OutputMatcher.new(nil, /octave/) + matcher.matches?(proc) + matcher.failure_message.should == + ["Expected:\n $stderr: /octave/\n", + " got:\n $stderr: \"unerror\"\n"] + end + + it "provides a useful negative failure message" do + proc = Proc.new { puts "expected"; $stderr.puts "error" } + matcher = OutputMatcher.new("expected", "error") + matcher.matches?(proc) + matcher.negative_failure_message.should == + ["Expected output not to be:\n", " $stdout: \"expected\"\n $stderr: \"error\"\n"] + matcher = OutputMatcher.new("expected", nil) + matcher.matches?(proc) + matcher.negative_failure_message.should == + ["Expected output not to be:\n", " $stdout: \"expected\"\n"] + matcher = OutputMatcher.new(nil, "error") + matcher.matches?(proc) + matcher.negative_failure_message.should == + ["Expected output not to be:\n", " $stderr: \"error\"\n"] + matcher = OutputMatcher.new(/expect/, nil) + matcher.matches?(proc) + matcher.negative_failure_message.should == + ["Expected output not to be:\n", " $stdout: \"expected\"\n"] + matcher = OutputMatcher.new(nil, /err/) + matcher.matches?(proc) + matcher.negative_failure_message.should == + ["Expected output not to be:\n", " $stderr: \"error\"\n"] + end +end diff --git a/spec/mspec/spec/matchers/output_to_fd_spec.rb b/spec/mspec/spec/matchers/output_to_fd_spec.rb new file mode 100644 index 0000000000..d35da58829 --- /dev/null +++ b/spec/mspec/spec/matchers/output_to_fd_spec.rb @@ -0,0 +1,42 @@ +require 'spec_helper' +require 'mspec/expectations/expectations' +require 'mspec/matchers' + +describe OutputToFDMatcher do + # Figure out how in the hell to achieve this + it "matches when running the block produces the expected output to the given FD" do + output_to_fd("Hi\n", STDERR).matches?(lambda { $stderr.print "Hi\n" }).should == true + end + + it "does not match if running the block does not produce the expected output to the FD" do + output_to_fd("Hi\n", STDERR).matches?(lambda { $stderr.puts("Hello\n") }).should == false + end + + it "propagate the exception if one is thrown while matching" do + exc = RuntimeError.new("propagates") + lambda { + output_to_fd("Hi\n", STDERR).matches?(lambda { + raise exc + }).should == false + }.should raise_error(exc) + end + + it "defaults to matching against STDOUT" do + output_to_fd("Hi\n").matches?(lambda { $stdout.print "Hi\n" }).should == true + end + + it "accepts any IO instance" do + io = IO.new STDOUT.fileno + output_to_fd("Hi\n", io).matches?(lambda { io.print "Hi\n" }).should == true + end + + it "allows matching with a Regexp" do + s = "Hi there\n" + output_to_fd(/Hi/, STDERR).matches?(lambda { $stderr.print s }).should == true + output_to_fd(/Hi?/, STDERR).matches?(lambda { $stderr.print s }).should == true + output_to_fd(/[hH]i?/, STDERR).matches?(lambda { $stderr.print s }).should == true + output_to_fd(/.*/, STDERR).matches?(lambda { $stderr.print s }).should == true + output_to_fd(/H.*?here/, STDERR).matches?(lambda { $stderr.print s }).should == true + output_to_fd(/Ahoy/, STDERR).matches?(lambda { $stderr.print s }).should == false + end +end diff --git a/spec/mspec/spec/matchers/raise_error_spec.rb b/spec/mspec/spec/matchers/raise_error_spec.rb new file mode 100644 index 0000000000..88aab34d53 --- /dev/null +++ b/spec/mspec/spec/matchers/raise_error_spec.rb @@ -0,0 +1,108 @@ +require 'spec_helper' +require 'mspec/expectations/expectations' +require 'mspec/matchers' + +class ExpectedException < Exception; end +class UnexpectedException < Exception; end + +describe RaiseErrorMatcher do + it "matches when the proc raises the expected exception" do + proc = Proc.new { raise ExpectedException } + matcher = RaiseErrorMatcher.new(ExpectedException, nil) + matcher.matches?(proc).should == true + end + + it "executes it's optional block if matched" do + run = false + proc = Proc.new { raise ExpectedException } + matcher = RaiseErrorMatcher.new(ExpectedException, nil) { |error| + run = true + error.class.should == ExpectedException + } + + matcher.matches?(proc).should == true + run.should == true + end + + it "matches when the proc raises the expected exception with the expected message" do + proc = Proc.new { raise ExpectedException, "message" } + matcher = RaiseErrorMatcher.new(ExpectedException, "message") + matcher.matches?(proc).should == true + end + + it "matches when the proc raises the expected exception with a matching message" do + proc = Proc.new { raise ExpectedException, "some message" } + matcher = RaiseErrorMatcher.new(ExpectedException, /some/) + matcher.matches?(proc).should == true + end + + it "does not match when the proc does not raise the expected exception" do + exc = UnexpectedException.new + matcher = RaiseErrorMatcher.new(ExpectedException, nil) + + matcher.matching_exception?(exc).should == false + lambda { + matcher.matches?(Proc.new { raise exc }) + }.should raise_error(UnexpectedException) + end + + it "does not match when the proc raises the expected exception with an unexpected message" do + exc = ExpectedException.new("unexpected") + matcher = RaiseErrorMatcher.new(ExpectedException, "expected") + + matcher.matching_exception?(exc).should == false + lambda { + matcher.matches?(Proc.new { raise exc }) + }.should raise_error(ExpectedException) + end + + it "does not match when the proc does not raise an exception" do + proc = Proc.new {} + matcher = RaiseErrorMatcher.new(ExpectedException, "expected") + matcher.matches?(proc).should == false + end + + it "provides a useful failure message" do + exc = UnexpectedException.new("unexpected") + matcher = RaiseErrorMatcher.new(ExpectedException, "expected") + + matcher.matching_exception?(exc).should == false + lambda { + matcher.matches?(Proc.new { raise exc }) + }.should raise_error(UnexpectedException) + matcher.failure_message.should == + ["Expected ExpectedException (expected)", "but got UnexpectedException (unexpected)"] + end + + it "provides a useful failure message when no exception is raised" do + proc = Proc.new { 120 } + matcher = RaiseErrorMatcher.new(ExpectedException, "expected") + matcher.matches?(proc) + matcher.failure_message.should == + ["Expected ExpectedException (expected)", "but no exception was raised (120 was returned)"] + end + + it "provides a useful failure message when no exception is raised and nil is returned" do + proc = Proc.new { nil } + matcher = RaiseErrorMatcher.new(ExpectedException, "expected") + matcher.matches?(proc) + matcher.failure_message.should == + ["Expected ExpectedException (expected)", "but no exception was raised (nil was returned)"] + end + + it "provides a useful negative failure message" do + proc = Proc.new { raise ExpectedException, "expected" } + matcher = RaiseErrorMatcher.new(ExpectedException, "expected") + matcher.matches?(proc) + matcher.negative_failure_message.should == + ["Expected to not get ExpectedException (expected)", ""] + end + + it "provides a useful negative failure message for strict subclasses of the matched exception class" do + proc = Proc.new { raise UnexpectedException, "unexpected" } + matcher = RaiseErrorMatcher.new(Exception, nil) + matcher.matches?(proc) + matcher.negative_failure_message.should == + ["Expected to not get Exception", "but got UnexpectedException (unexpected)"] + end +end diff --git a/spec/mspec/spec/matchers/respond_to_spec.rb b/spec/mspec/spec/matchers/respond_to_spec.rb new file mode 100644 index 0000000000..988caf4dff --- /dev/null +++ b/spec/mspec/spec/matchers/respond_to_spec.rb @@ -0,0 +1,33 @@ +require 'spec_helper' +require 'mspec/expectations/expectations' +require 'mspec/matchers' + +describe RespondToMatcher do + it "matches when actual does respond_to? expected" do + RespondToMatcher.new(:to_s).matches?(Object.new).should == true + RespondToMatcher.new(:inject).matches?([]).should == true + RespondToMatcher.new(:[]).matches?(1).should == true + RespondToMatcher.new(:[]=).matches?("string").should == true + end + + it "does not match when actual does not respond_to? expected" do + RespondToMatcher.new(:to_i).matches?(Object.new).should == false + RespondToMatcher.new(:inject).matches?(1).should == false + RespondToMatcher.new(:non_existent_method).matches?([]).should == false + RespondToMatcher.new(:[]=).matches?(1).should == false + end + + it "provides a useful failure message" do + matcher = RespondToMatcher.new(:non_existent_method) + matcher.matches?('string') + matcher.failure_message.should == [ + "Expected \"string\" (String)", "to respond to non_existent_method"] + end + + it "provides a useful negative failure message" do + matcher = RespondToMatcher.new(:to_i) + matcher.matches?(4.0) + matcher.negative_failure_message.should == [ + "Expected 4.0 (Float)", "not to respond to to_i"] + end +end diff --git a/spec/mspec/spec/matchers/signed_zero_spec.rb b/spec/mspec/spec/matchers/signed_zero_spec.rb new file mode 100644 index 0000000000..9c5c50c602 --- /dev/null +++ b/spec/mspec/spec/matchers/signed_zero_spec.rb @@ -0,0 +1,32 @@ +require 'spec_helper' +require 'mspec/expectations/expectations' +require 'mspec/matchers' + +describe SignedZeroMatcher do + it "matches when actual is zero and has the correct sign" do + SignedZeroMatcher.new(1).matches?(0.0).should == true + SignedZeroMatcher.new(-1).matches?(-0.0).should == true + end + + it "does not match when actual is non-zero" do + SignedZeroMatcher.new(1).matches?(1.0).should == false + SignedZeroMatcher.new(-1).matches?(-1.0).should == false + end + + it "does not match when actual is zero but has the incorrect sign" do + SignedZeroMatcher.new(1).matches?(-0.0).should == false + SignedZeroMatcher.new(-1).matches?(0.0).should == false + end + + it "provides a useful failure message" do + matcher = SignedZeroMatcher.new(-1) + matcher.matches?(0.0) + matcher.failure_message.should == ["Expected 0.0", "to be -0.0"] + end + + it "provides a useful negative failure message" do + matcher = SignedZeroMatcher.new(-1) + matcher.matches?(-0.0) + matcher.negative_failure_message.should == ["Expected -0.0", "not to be -0.0"] + end +end diff --git a/spec/mspec/spec/mocks/mock_spec.rb b/spec/mspec/spec/mocks/mock_spec.rb new file mode 100644 index 0000000000..1a7fb2d567 --- /dev/null +++ b/spec/mspec/spec/mocks/mock_spec.rb @@ -0,0 +1,469 @@ +# This is a bit awkward. Currently the way to verify that the +# opposites are true (for example a failure when the specified +# arguments are NOT provided) is to simply alter the particular +# spec to a failure condition. +require 'spec_helper' +require 'mspec/runner/mspec' +require 'mspec/mocks/mock' +require 'mspec/mocks/proxy' + +describe Mock, ".mocks" do + it "returns a Hash" do + Mock.mocks.should be_kind_of(Hash) + end +end + +describe Mock, ".stubs" do + it "returns a Hash" do + Mock.stubs.should be_kind_of(Hash) + end +end + +describe Mock, ".replaced_name" do + it "returns the name for a method that is being replaced by a mock method" do + m = double('a fake id') + m.stub(:__mspec_object_id__).and_return(42) + Mock.replaced_name(m, :method_call).should == :__mspec_42_method_call__ + end +end + +describe Mock, ".replaced_key" do + it "returns a key used internally by Mock" do + m = double('a fake id') + m.stub(:__mspec_object_id__).and_return(42) + Mock.replaced_key(m, :method_call).should == [:__mspec_42_method_call__, :method_call] + end +end + +describe Mock, ".replaced?" do + before :each do + @mock = double('install_method') + MSpec.stub(:actions) + MSpec.stub(:current).and_return(double("spec state").as_null_object) + end + + it "returns true if a method has been stubbed on an object" do + Mock.install_method @mock, :method_call + Mock.replaced?(Mock.replaced_name(@mock, :method_call)).should be_true + end + + it "returns true if a method has been mocked on an object" do + Mock.install_method @mock, :method_call, :stub + Mock.replaced?(Mock.replaced_name(@mock, :method_call)).should be_true + end + + it "returns false if a method has not been stubbed or mocked" do + Mock.replaced?(Mock.replaced_name(@mock, :method_call)).should be_false + end +end + +describe Mock, ".name_or_inspect" do + before :each do + @mock = double("I have a #name") + end + + it "returns the value of @name if set" do + @mock.instance_variable_set(:@name, "Myself") + Mock.name_or_inspect(@mock).should == "Myself" + end +end + +describe Mock, ".install_method for mocks" do + before :each do + @mock = double('install_method') + MSpec.stub(:actions) + MSpec.stub(:current).and_return(double("spec state").as_null_object) + end + + after :each do + Mock.cleanup + end + + it "returns a MockProxy instance" do + Mock.install_method(@mock, :method_call).should be_an_instance_of(MockProxy) + end + + it "does not override a previously mocked method with the same name" do + Mock.install_method(@mock, :method_call).with(:a, :b).and_return(1) + Mock.install_method(@mock, :method_call).with(:c).and_return(2) + @mock.method_call(:a, :b) + @mock.method_call(:c) + lambda { @mock.method_call(:d) }.should raise_error(SpecExpectationNotMetError) + end + + # This illustrates RSpec's behavior. This spec fails in mock call count verification + # on RSpec (i.e. Mock 'foo' expected :foo with (any args) once, but received it 0 times) + # and we mimic the behavior of RSpec. + # + # describe "A mock receiving multiple calls to #should_receive" do + # it "returns the first value mocked" do + # m = mock 'multiple #should_receive' + # m.should_receive(:foo).and_return(true) + # m.foo.should == true + # m.should_receive(:foo).and_return(false) + # m.foo.should == true + # end + # end + # + it "does not override a previously mocked method having the same arguments" do + Mock.install_method(@mock, :method_call).with(:a).and_return(true) + @mock.method_call(:a).should == true + Mock.install_method(@mock, :method_call).with(:a).and_return(false) + @mock.method_call(:a).should == true + lambda { Mock.verify_count }.should raise_error(SpecExpectationNotMetError) + end + + it "properly sends #respond_to? calls to the aliased respond_to? method when not matching mock expectations" do + Mock.install_method(@mock, :respond_to?).with(:to_str).and_return('mock to_str') + Mock.install_method(@mock, :respond_to?).with(:to_int).and_return('mock to_int') + @mock.respond_to?(:to_str).should == 'mock to_str' + @mock.respond_to?(:to_int).should == 'mock to_int' + @mock.respond_to?(:to_s).should == true + @mock.respond_to?(:not_really_a_real_method_seriously).should == false + end + + it "adds to the expectation tally" do + state = double("run state").as_null_object + state.stub(:state).and_return(double("spec state")) + MSpec.should_receive(:current).and_return(state) + MSpec.should_receive(:actions).with(:expectation, state.state) + Mock.install_method(@mock, :method_call).and_return(1) + @mock.method_call.should == 1 + end + + it "registers that an expectation has been encountered" do + state = double("run state").as_null_object + state.stub(:state).and_return(double("spec state")) + MSpec.should_receive(:expectation) + Mock.install_method(@mock, :method_call).and_return(1) + @mock.method_call.should == 1 + end +end + +describe Mock, ".install_method for stubs" do + before :each do + @mock = double('install_method') + MSpec.stub(:actions) + MSpec.stub(:current).and_return(double("spec state").as_null_object) + end + + after :each do + Mock.cleanup + end + + it "returns a MockProxy instance" do + Mock.install_method(@mock, :method_call, :stub).should be_an_instance_of(MockProxy) + end + + # This illustrates RSpec's behavior. This spec passes on RSpec and we mimic it + # + # describe "A mock receiving multiple calls to #stub" do + # it "returns the last value stubbed" do + # m = mock 'multiple #stub' + # m.stub(:foo).and_return(true) + # m.foo.should == true + # m.stub(:foo).and_return(false) + # m.foo.should == false + # end + # end + it "inserts new stubs before old stubs" do + Mock.install_method(@mock, :method_call, :stub).with(:a).and_return(true) + @mock.method_call(:a).should == true + Mock.install_method(@mock, :method_call, :stub).with(:a).and_return(false) + @mock.method_call(:a).should == false + Mock.verify_count + end + + it "does not add to the expectation tally" do + state = double("run state").as_null_object + state.stub(:state).and_return(double("spec state")) + MSpec.should_not_receive(:actions) + Mock.install_method(@mock, :method_call, :stub).and_return(1) + @mock.method_call.should == 1 + end +end + +describe Mock, ".install_method" do + before :each do + @mock = double('install_method') + MSpec.stub(:actions) + MSpec.stub(:current).and_return(double("spec state").as_null_object) + end + + after :each do + Mock.cleanup + end + + it "does not alias a mocked or stubbed method when installing a new mock or stub" do + @mock.should_not respond_to(:method_call) + + Mock.install_method @mock, :method_call + @mock.should respond_to(:method_call) + @mock.should_not respond_to(Mock.replaced_name(@mock, :method_call)) + + Mock.install_method @mock, :method_call, :stub + @mock.should respond_to(:method_call) + @mock.should_not respond_to(Mock.replaced_name(@mock, :method_call)) + end +end + +class MockAndRaiseError < Exception; end + +describe Mock, ".verify_call" do + before :each do + MSpec.stub(:actions) + MSpec.stub(:current).and_return(double("spec state").as_null_object) + + @mock = double('verify_call') + @proxy = Mock.install_method @mock, :method_call + end + + after :each do + ScratchPad.clear + Mock.cleanup + end + + it "does not raise an exception when the mock method receives the expected arguments" do + @proxy.with(1, 'two', :three) + Mock.verify_call @mock, :method_call, 1, 'two', :three + end + + it "raises an SpecExpectationNotMetError when the mock method does not receive the expected arguments" do + @proxy.with(4, 2) + lambda { + Mock.verify_call @mock, :method_call, 42 + }.should raise_error(SpecExpectationNotMetError) + end + + it "raises an SpecExpectationNotMetError when the mock method is called with arguments but expects none" do + lambda { + @proxy.with(:no_args) + Mock.verify_call @mock, :method_call, "hello" + }.should raise_error(SpecExpectationNotMetError) + end + + it "raises an SpecExpectationNotMetError when the mock method is called with no arguments but expects some" do + @proxy.with("hello", "beautiful", "world") + lambda { + Mock.verify_call @mock, :method_call + }.should raise_error(SpecExpectationNotMetError) + end + + it "does not raise an exception when the mock method is called with arguments and is expecting :any_args" do + @proxy.with(:any_args) + Mock.verify_call @mock, :method_call, 1, 2, 3 + end + + it "yields a passed block when it is expected to" do + @proxy.and_yield() + Mock.verify_call @mock, :method_call do + ScratchPad.record true + end + ScratchPad.recorded.should == true + end + + it "does not yield a passed block when it is not expected to" do + Mock.verify_call @mock, :method_call do + ScratchPad.record true + end + ScratchPad.recorded.should == nil + end + + it "can yield subsequently" do + @proxy.and_yield(1).and_yield(2).and_yield(3) + + ScratchPad.record [] + Mock.verify_call @mock, :method_call do |arg| + ScratchPad << arg + end + ScratchPad.recorded.should == [1, 2, 3] + end + + it "can yield and return an expected value" do + @proxy.and_yield(1).and_return(3) + + Mock.verify_call(@mock, :method_call) { |arg| ScratchPad.record arg }.should == 3 + ScratchPad.recorded.should == 1 + end + + it "raises an expection when it is expected to yield but no block is given" do + @proxy.and_yield(1, 2, 3) + lambda { + Mock.verify_call(@mock, :method_call) + }.should raise_error(SpecExpectationNotMetError) + end + + it "raises an expection when it is expected to yield more arguments than the block can take" do + @proxy.and_yield(1, 2, 3) + lambda { + Mock.verify_call(@mock, :method_call) {|a, b|} + }.should raise_error(SpecExpectationNotMetError) + end + + it "does not raise an expection when it is expected to yield to a block that can take any number of arguments" do + @proxy.and_yield(1, 2, 3) + expect { + Mock.verify_call(@mock, :method_call) {|*a|} + }.not_to raise_error + end + + it "raises an exception when expected to" do + @proxy.and_raise(MockAndRaiseError) + lambda { + Mock.verify_call @mock, :method_call + }.should raise_error(MockAndRaiseError) + end +end + +describe Mock, ".verify_count" do + before :each do + MSpec.stub(:actions) + MSpec.stub(:current).and_return(double("spec state").as_null_object) + + @mock = double('verify_count') + @proxy = Mock.install_method @mock, :method_call + end + + after :each do + Mock.cleanup + end + + it "does not raise an exception when the mock receives at least the expected number of calls" do + @proxy.at_least(2) + @mock.method_call + @mock.method_call + Mock.verify_count + end + + it "raises an SpecExpectationNotMetError when the mock receives less than at least the expected number of calls" do + @proxy.at_least(2) + @mock.method_call + lambda { Mock.verify_count }.should raise_error(SpecExpectationNotMetError) + end + + it "does not raise an exception when the mock receives at most the expected number of calls" do + @proxy.at_most(2) + @mock.method_call + @mock.method_call + Mock.verify_count + end + + it "raises an SpecExpectationNotMetError when the mock receives more than at most the expected number of calls" do + @proxy.at_most(2) + @mock.method_call + @mock.method_call + @mock.method_call + lambda { Mock.verify_count }.should raise_error(SpecExpectationNotMetError) + end + + it "does not raise an exception when the mock receives exactly the expected number of calls" do + @proxy.exactly(2) + @mock.method_call + @mock.method_call + Mock.verify_count + end + + it "raises an SpecExpectationNotMetError when the mock receives less than exactly the expected number of calls" do + @proxy.exactly(2) + @mock.method_call + lambda { Mock.verify_count }.should raise_error(SpecExpectationNotMetError) + end + + it "raises an SpecExpectationNotMetError when the mock receives more than exactly the expected number of calls" do + @proxy.exactly(2) + @mock.method_call + @mock.method_call + @mock.method_call + lambda { Mock.verify_count }.should raise_error(SpecExpectationNotMetError) + end +end + +describe Mock, ".verify_count mixing mocks and stubs" do + before :each do + MSpec.stub(:actions) + MSpec.stub(:current).and_return(double("spec state").as_null_object) + + @mock = double('verify_count') + end + + after :each do + Mock.cleanup + end + + it "does not raise an exception for a stubbed method that is never called" do + Mock.install_method @mock, :method_call, :stub + Mock.verify_count + end + + it "verifies the calls to the mocked method when a mock is defined after a stub" do + Mock.install_method @mock, :method_call, :stub + Mock.install_method @mock, :method_call, :mock + @mock.method_call + Mock.verify_count + end + + it "verifies the calls to the mocked method when a mock is defined before a stub" do + Mock.install_method @mock, :method_call, :mock + Mock.install_method @mock, :method_call, :stub + @mock.method_call + Mock.verify_count + end +end + +describe Mock, ".cleanup" do + before :each do + MSpec.stub(:actions) + MSpec.stub(:current).and_return(double("spec state").as_null_object) + + @mock = double('cleanup') + @proxy = Mock.install_method @mock, :method_call + @stub = Mock.install_method @mock, :method_call, :stub + end + + after :each do + Mock.cleanup + end + + it "removes the mock method call if it did not override an existing method" do + @mock.should respond_to(:method_call) + + Mock.cleanup + @mock.should_not respond_to(:method_call) + end + + it "removes the replaced method if the mock method overrides an existing method" do + def @mock.already_here() :hey end + @mock.should respond_to(:already_here) + replaced_name = Mock.replaced_name(@mock, :already_here) + Mock.install_method @mock, :already_here + @mock.should respond_to(replaced_name) + + Mock.cleanup + @mock.should_not respond_to(replaced_name) + @mock.should respond_to(:already_here) + @mock.already_here.should == :hey + end + + it "removes all mock expectations" do + Mock.mocks.should == { Mock.replaced_key(@mock, :method_call) => [@proxy] } + Mock.cleanup + Mock.mocks.should == {} + end + + it "removes all stubs" do + Mock.stubs.should == { Mock.replaced_key(@mock, :method_call) => [@stub] } + Mock.cleanup + Mock.stubs.should == {} + end + + it "removes the replaced name for mocks" do + replaced_key = Mock.replaced_key(@mock, :method_call) + Mock.should_receive(:clear_replaced).with(replaced_key) + + replaced_name = Mock.replaced_name(@mock, :method_call) + Mock.replaced?(replaced_name).should be_true + + Mock.cleanup + Mock.replaced?(replaced_name).should be_false + end +end diff --git a/spec/mspec/spec/mocks/proxy_spec.rb b/spec/mspec/spec/mocks/proxy_spec.rb new file mode 100644 index 0000000000..d9e754b972 --- /dev/null +++ b/spec/mspec/spec/mocks/proxy_spec.rb @@ -0,0 +1,405 @@ +require 'spec_helper' +require 'mspec/mocks/proxy' + +describe MockObject, ".new" do + it "creates a new mock object" do + m = MockObject.new('not a null object') + lambda { m.not_a_method }.should raise_error(NoMethodError) + end + + it "creates a new mock object that follows the NullObject pattern" do + m = MockObject.new('null object', :null_object => true) + m.not_really_a_method.should equal(m) + end +end + +describe MockProxy, ".new" do + it "creates a mock proxy by default" do + MockProxy.new.mock?.should be_true + end + + it "creates a stub proxy by request" do + MockProxy.new(:stub).stub?.should be_true + end + + it "sets the call expectation to 1 call for a mock" do + MockProxy.new.count.should == [:exactly, 1] + end + + it "sets the call expectation to any number of times for a stub" do + MockProxy.new(:stub).count.should == [:any_number_of_times, 0] + end +end + +describe MockProxy, "#count" do + before :each do + @proxy = MockProxy.new + end + + it "returns the expected number of calls the mock should receive" do + @proxy.count.should == [:exactly, 1] + @proxy.at_least(3).count.should == [:at_least, 3] + end +end + +describe MockProxy, "#arguments" do + before :each do + @proxy = MockProxy.new + end + + it "returns the expected arguments" do + @proxy.arguments.should == :any_args + end +end + +describe MockProxy, "#with" do + before :each do + @proxy = MockProxy.new + end + + it "returns self" do + @proxy.with(:a).should be_equal(@proxy) + end + + it "raises an ArgumentError if no arguments are given" do + lambda { @proxy.with }.should raise_error(ArgumentError) + end + + it "accepts any number of arguments" do + @proxy.with(1, 2, 3).should be_an_instance_of(MockProxy) + @proxy.arguments.should == [1,2,3] + end +end + +describe MockProxy, "#once" do + before :each do + @proxy = MockProxy.new + end + + it "returns self" do + @proxy.once.should be_equal(@proxy) + end + + it "sets the expected calls to 1" do + @proxy.once + @proxy.count.should == [:exactly, 1] + end + + it "accepts no arguments" do + lambda { @proxy.once(:a) }.should raise_error + end +end + +describe MockProxy, "#twice" do + before :each do + @proxy = MockProxy.new + end + + it "returns self" do + @proxy.twice.should be_equal(@proxy) + end + + it "sets the expected calls to 2" do + @proxy.twice + @proxy.count.should == [:exactly, 2] + end + + it "accepts no arguments" do + lambda { @proxy.twice(:b) }.should raise_error + end +end + +describe MockProxy, "#exactly" do + before :each do + @proxy = MockProxy.new + end + + it "returns self" do + @proxy.exactly(2).should be_equal(@proxy) + end + + it "sets the expected calls to exactly n" do + @proxy.exactly(5) + @proxy.count.should == [:exactly, 5] + end + + it "does not accept an argument that Integer() cannot convert" do + lambda { @proxy.exactly('x') }.should raise_error + end +end + +describe MockProxy, "#at_least" do + before :each do + @proxy = MockProxy.new + end + + it "returns self" do + @proxy.at_least(3).should be_equal(@proxy) + end + + it "sets the expected calls to at least n" do + @proxy.at_least(3) + @proxy.count.should == [:at_least, 3] + end + + it "accepts :once :twice" do + @proxy.at_least(:once) + @proxy.count.should == [:at_least, 1] + @proxy.at_least(:twice) + @proxy.count.should == [:at_least, 2] + end + + it "does not accept an argument that Integer() cannot convert" do + lambda { @proxy.at_least('x') }.should raise_error + end +end + +describe MockProxy, "#at_most" do + before :each do + @proxy = MockProxy.new + end + + it "returns self" do + @proxy.at_most(2).should be_equal(@proxy) + end + + it "sets the expected calls to at most n" do + @proxy.at_most(2) + @proxy.count.should == [:at_most, 2] + end + + it "accepts :once, :twice" do + @proxy.at_most(:once) + @proxy.count.should == [:at_most, 1] + @proxy.at_most(:twice) + @proxy.count.should == [:at_most, 2] + end + + it "does not accept an argument that Integer() cannot convert" do + lambda { @proxy.at_most('x') }.should raise_error + end +end + +describe MockProxy, "#any_number_of_times" do + before :each do + @proxy = MockProxy.new + end + + it "returns self" do + @proxy.any_number_of_times.should be_equal(@proxy) + end + + it "sets the expected calls to any number of times" do + @proxy.any_number_of_times + @proxy.count.should == [:any_number_of_times, 0] + end + + it "does not accept an argument" do + lambda { @proxy.any_number_of_times(2) }.should raise_error + end +end + +describe MockProxy, "#and_return" do + before :each do + @proxy = MockProxy.new + end + + it "returns self" do + @proxy.and_return(false).should equal(@proxy) + end + + it "sets the expected return value" do + @proxy.and_return(false) + @proxy.returning.should == false + end + + it "accepts any number of return values" do + @proxy.and_return(1, 2, 3) + @proxy.returning.should == 1 + @proxy.returning.should == 2 + @proxy.returning.should == 3 + end + + it "implicitly sets the expected number of calls" do + @proxy.and_return(1, 2, 3) + @proxy.count.should == [:exactly, 3] + end + + it "only sets the expected number of calls if it is higher than what is already set" do + @proxy.at_least(5).times.and_return(1, 2, 3) + @proxy.count.should == [:at_least, 5] + + @proxy.at_least(2).times.and_return(1, 2, 3) + @proxy.count.should == [:at_least, 3] + end +end + +describe MockProxy, "#returning" do + before :each do + @proxy = MockProxy.new + end + + it "returns nil by default" do + @proxy.returning.should be_nil + end + + it "returns the value set by #and_return" do + @proxy.and_return(2) + @proxy.returning.should == 2 + @proxy.returning.should == 2 + end + + it "returns a sequence of values set by #and_return" do + @proxy.and_return(1,2,3,4) + @proxy.returning.should == 1 + @proxy.returning.should == 2 + @proxy.returning.should == 3 + @proxy.returning.should == 4 + @proxy.returning.should == 4 + @proxy.returning.should == 4 + end +end + +describe MockProxy, "#calls" do + before :each do + @proxy = MockProxy.new + end + + it "returns the number of times the proxy is called" do + @proxy.calls.should == 0 + end +end + +describe MockProxy, "#called" do + before :each do + @proxy = MockProxy.new + end + + it "increments the number of times the proxy is called" do + @proxy.called + @proxy.called + @proxy.calls.should == 2 + end +end + +describe MockProxy, "#times" do + before :each do + @proxy = MockProxy.new + end + + it "is a no-op" do + @proxy.times.should == @proxy + end +end + +describe MockProxy, "#stub?" do + it "returns true if the proxy is created as a stub" do + MockProxy.new(:stub).stub?.should be_true + end + + it "returns false if the proxy is created as a mock" do + MockProxy.new(:mock).stub?.should be_false + end +end + +describe MockProxy, "#mock?" do + it "returns true if the proxy is created as a mock" do + MockProxy.new(:mock).mock?.should be_true + end + + it "returns false if the proxy is created as a stub" do + MockProxy.new(:stub).mock?.should be_false + end +end + +describe MockProxy, "#and_yield" do + before :each do + @proxy = MockProxy.new + end + + it "returns self" do + @proxy.and_yield(false).should equal(@proxy) + end + + it "sets the expected values to yield" do + @proxy.and_yield(1).yielding.should == [[1]] + end + + it "accepts multiple values to yield" do + @proxy.and_yield(1, 2, 3).yielding.should == [[1, 2, 3]] + end +end + +describe MockProxy, "#raising" do + before :each do + @proxy = MockProxy.new + end + + it "returns nil by default" do + @proxy.raising.should be_nil + end + + it "returns the exception object passed to #and_raise" do + exc = double("exception") + @proxy.and_raise(exc) + @proxy.raising.should equal(exc) + end + + it "returns an instance of RuntimeError when a String is passed to #and_raise" do + @proxy.and_raise("an error") + exc = @proxy.raising + exc.should be_an_instance_of(RuntimeError) + exc.message.should == "an error" + end +end + +describe MockProxy, "#yielding" do + before :each do + @proxy = MockProxy.new + end + + it "returns an empty array by default" do + @proxy.yielding.should == [] + end + + it "returns an array of arrays of values the proxy should yield" do + @proxy.and_yield(3) + @proxy.yielding.should == [[3]] + end + + it "returns an accumulation of arrays of values the proxy should yield" do + @proxy.and_yield(1).and_yield(2, 3) + @proxy.yielding.should == [[1], [2, 3]] + end +end + +describe MockProxy, "#yielding?" do + before :each do + @proxy = MockProxy.new + end + + it "returns false if the proxy is not yielding" do + @proxy.yielding?.should be_false + end + + it "returns true if the proxy is yielding" do + @proxy.and_yield(1) + @proxy.yielding?.should be_true + end +end + +describe MockIntObject, "#to_int" do + before :each do + @int = MockIntObject.new(10) + end + + it "returns the number if to_int is called" do + @int.to_int.should == 10 + @int.count.should == [:at_least, 1] + end + + it "tries to convert the target to int if to_int is called" do + MockIntObject.new(@int).to_int.should == 10 + @int.count.should == [:at_least, 1] + end +end diff --git a/spec/mspec/spec/runner/actions/filter_spec.rb b/spec/mspec/spec/runner/actions/filter_spec.rb new file mode 100644 index 0000000000..d185781757 --- /dev/null +++ b/spec/mspec/spec/runner/actions/filter_spec.rb @@ -0,0 +1,84 @@ +require File.dirname(__FILE__) + '/../../spec_helper' +require 'mspec/runner/actions/filter' +require 'mspec/runner/mspec' +require 'mspec/runner/tag' + +describe ActionFilter do + it "creates a filter when not passed a description" do + MatchFilter.should_not_receive(:new) + ActionFilter.new(nil, nil) + end + + it "creates a filter from a single description" do + MatchFilter.should_receive(:new).with(nil, "match me") + ActionFilter.new(nil, "match me") + end + + it "creates a filter from an array of descriptions" do + MatchFilter.should_receive(:new).with(nil, "match me", "again") + ActionFilter.new(nil, ["match me", "again"]) + end +end + +describe ActionFilter, "#===" do + before :each do + MSpec.stub(:read_tags).and_return(["match"]) + @action = ActionFilter.new(nil, ["catch", "if you"]) + end + + it "returns false if there are no filters" do + action = ActionFilter.new + action.===("anything").should == false + end + + it "returns true if the argument matches any of the descriptions" do + @action.===("catch").should == true + @action.===("if you can").should == true + end + + it "returns false if the argument does not match any of the descriptions" do + @action.===("patch me").should == false + @action.===("if I can").should == false + end +end + +describe ActionFilter, "#load" do + before :each do + @tag = SpecTag.new "tag(comment):description" + end + + it "creates a filter from a single tag" do + MSpec.should_receive(:read_tags).with(["tag"]).and_return([@tag]) + MatchFilter.should_receive(:new).with(nil, "description") + ActionFilter.new("tag", nil).load + end + + it "creates a filter from an array of tags" do + MSpec.should_receive(:read_tags).with(["tag", "key"]).and_return([@tag]) + MatchFilter.should_receive(:new).with(nil, "description") + ActionFilter.new(["tag", "key"], nil).load + end + + it "creates a filter from both tags and descriptions" do + MSpec.should_receive(:read_tags).and_return([@tag]) + filter = ActionFilter.new("tag", ["match me", "again"]) + MatchFilter.should_receive(:new).with(nil, "description") + filter.load + end +end + +describe ActionFilter, "#register" do + it "registers itself with MSpec for the :load actions" do + filter = ActionFilter.new + MSpec.should_receive(:register).with(:load, filter) + filter.register + end +end + +describe ActionFilter, "#unregister" do + it "unregisters itself with MSpec for the :load actions" do + filter = ActionFilter.new + MSpec.should_receive(:unregister).with(:load, filter) + filter.unregister + end +end diff --git a/spec/mspec/spec/runner/actions/tag_spec.rb b/spec/mspec/spec/runner/actions/tag_spec.rb new file mode 100644 index 0000000000..92df362d02 --- /dev/null +++ b/spec/mspec/spec/runner/actions/tag_spec.rb @@ -0,0 +1,315 @@ +require File.dirname(__FILE__) + '/../../spec_helper' +require 'mspec/runner/actions/tag' +require 'mspec/runner/mspec' +require 'mspec/runner/example' +require 'mspec/runner/tag' + +describe TagAction, ".new" do + it "creates an MatchFilter with its tag and desc arguments" do + filter = double('action filter').as_null_object + MatchFilter.should_receive(:new).with(nil, "some", "thing").and_return(filter) + TagAction.new :add, :all, nil, nil, ["tag", "key"], ["some", "thing"] + end +end + +describe TagAction, "#===" do + before :each do + MSpec.stub(:read_tags).and_return(["match"]) + @action = TagAction.new :add, :fail, nil, nil, nil, ["catch", "if you"] + end + + it "returns true if there are no filters" do + action = TagAction.new :add, :all, nil, nil + action.===("anything").should == true + end + + it "returns true if the argument matches any of the descriptions" do + @action.===("catch").should == true + @action.===("if you can").should == true + end + + it "returns false if the argument does not match any of the descriptions" do + @action.===("patch me").should == false + @action.===("if I can").should == false + end +end + +describe TagAction, "#exception?" do + before :each do + @action = TagAction.new :add, :fail, nil, nil, nil, nil + end + + it "returns false if no exception has been raised while evaluating an example" do + @action.exception?.should be_false + end + + it "returns true if an exception was raised while evaluating an example" do + @action.exception ExceptionState.new nil, nil, Exception.new("failed") + @action.exception?.should be_true + end +end + +describe TagAction, "#outcome?" do + before :each do + MSpec.stub(:read_tags).and_return([]) + @exception = ExceptionState.new nil, nil, Exception.new("failed") + end + + it "returns true if outcome is :fail and the spec fails" do + action = TagAction.new :add, :fail, nil, nil, nil, nil + action.exception @exception + action.outcome?.should == true + end + + it "returns false if the outcome is :fail and the spec passes" do + action = TagAction.new :add, :fail, nil, nil, nil, nil + action.outcome?.should == false + end + + it "returns true if the outcome is :pass and the spec passes" do + action = TagAction.new :del, :pass, nil, nil, nil, nil + action.outcome?.should == true + end + + it "returns false if the outcome is :pass and the spec fails" do + action = TagAction.new :del, :pass, nil, nil, nil, nil + action.exception @exception + action.outcome?.should == false + end + + it "returns true if the outcome is :all" do + action = TagAction.new :add, :all, nil, nil, nil, nil + action.exception @exception + action.outcome?.should == true + end +end + +describe TagAction, "#before" do + it "resets the #exception? flag to false" do + action = TagAction.new :add, :fail, nil, nil, nil, nil + action.exception?.should be_false + action.exception ExceptionState.new(nil, nil, Exception.new("Fail!")) + action.exception?.should be_true + action.before(ExampleState.new(ContextState.new("describe"), "it")) + action.exception?.should be_false + end +end + +describe TagAction, "#exception" do + it "sets the #exception? flag" do + action = TagAction.new :add, :fail, nil, nil, nil, nil + action.exception?.should be_false + action.exception ExceptionState.new(nil, nil, Exception.new("Fail!")) + action.exception?.should be_true + end +end + +describe TagAction, "#after when action is :add" do + before :each do + MSpec.stub(:read_tags).and_return([]) + context = ContextState.new "Catch#me" + @state = ExampleState.new context, "if you can" + @tag = SpecTag.new "tag(comment):Catch#me if you can" + SpecTag.stub(:new).and_return(@tag) + @exception = ExceptionState.new nil, nil, Exception.new("failed") + end + + it "does not write a tag if the description does not match" do + MSpec.should_not_receive(:write_tag) + action = TagAction.new :add, :all, "tag", "comment", nil, "match" + action.after @state + end + + it "does not write a tag if outcome is :fail and the spec passed" do + MSpec.should_not_receive(:write_tag) + action = TagAction.new :add, :fail, "tag", "comment", nil, "can" + action.after @state + end + + it "writes a tag if the outcome is :fail and the spec failed" do + MSpec.should_receive(:write_tag).with(@tag) + action = TagAction.new :add, :fail, "tag", "comment", nil, "can" + action.exception @exception + action.after @state + end + + it "does not write a tag if outcome is :pass and the spec failed" do + MSpec.should_not_receive(:write_tag) + action = TagAction.new :add, :pass, "tag", "comment", nil, "can" + action.exception @exception + action.after @state + end + + it "writes a tag if the outcome is :pass and the spec passed" do + MSpec.should_receive(:write_tag).with(@tag) + action = TagAction.new :add, :pass, "tag", "comment", nil, "can" + action.after @state + end + + it "writes a tag if the outcome is :all" do + MSpec.should_receive(:write_tag).with(@tag) + action = TagAction.new :add, :all, "tag", "comment", nil, "can" + action.after @state + end +end + +describe TagAction, "#after when action is :del" do + before :each do + MSpec.stub(:read_tags).and_return([]) + context = ContextState.new "Catch#me" + @state = ExampleState.new context, "if you can" + @tag = SpecTag.new "tag(comment):Catch#me if you can" + SpecTag.stub(:new).and_return(@tag) + @exception = ExceptionState.new nil, nil, Exception.new("failed") + end + + it "does not delete a tag if the description does not match" do + MSpec.should_not_receive(:delete_tag) + action = TagAction.new :del, :all, "tag", "comment", nil, "match" + action.after @state + end + + it "does not delete a tag if outcome is :fail and the spec passed" do + MSpec.should_not_receive(:delete_tag) + action = TagAction.new :del, :fail, "tag", "comment", nil, "can" + action.after @state + end + + it "deletes a tag if the outcome is :fail and the spec failed" do + MSpec.should_receive(:delete_tag).with(@tag) + action = TagAction.new :del, :fail, "tag", "comment", nil, "can" + action.exception @exception + action.after @state + end + + it "does not delete a tag if outcome is :pass and the spec failed" do + MSpec.should_not_receive(:delete_tag) + action = TagAction.new :del, :pass, "tag", "comment", nil, "can" + action.exception @exception + action.after @state + end + + it "deletes a tag if the outcome is :pass and the spec passed" do + MSpec.should_receive(:delete_tag).with(@tag) + action = TagAction.new :del, :pass, "tag", "comment", nil, "can" + action.after @state + end + + it "deletes a tag if the outcome is :all" do + MSpec.should_receive(:delete_tag).with(@tag) + action = TagAction.new :del, :all, "tag", "comment", nil, "can" + action.after @state + end +end + +describe TagAction, "#finish" do + before :each do + $stdout = @out = IOStub.new + context = ContextState.new "Catch#me" + @state = ExampleState.new context, "if you can" + MSpec.stub(:write_tag).and_return(true) + MSpec.stub(:delete_tag).and_return(true) + end + + after :each do + $stdout = STDOUT + end + + it "reports no specs tagged if none where tagged" do + action = TagAction.new :add, :fail, "tag", "comment", nil, "can" + action.stub(:outcome?).and_return(false) + action.after @state + action.finish + @out.should == "\nTagAction: no specs were tagged with 'tag'\n" + end + + it "reports no specs tagged if none where tagged" do + action = TagAction.new :del, :fail, "tag", "comment", nil, "can" + action.stub(:outcome?).and_return(false) + action.after @state + action.finish + @out.should == "\nTagAction: no tags 'tag' were deleted\n" + end + + it "reports the spec descriptions that were tagged" do + action = TagAction.new :add, :fail, "tag", "comment", nil, "can" + action.stub(:outcome?).and_return(true) + action.after @state + action.finish + @out.should == +%[ +TagAction: specs tagged with 'tag': + +Catch#me if you can +] + end + + it "reports the spec descriptions for the tags that were deleted" do + action = TagAction.new :del, :fail, "tag", "comment", nil, "can" + action.stub(:outcome?).and_return(true) + action.after @state + action.finish + @out.should == +%[ +TagAction: tag 'tag' deleted for specs: + +Catch#me if you can +] + end +end + +describe TagAction, "#register" do + before :each do + MSpec.stub(:register) + MSpec.stub(:read_tags).and_return([]) + @action = TagAction.new :add, :all, nil, nil, nil, nil + end + + it "registers itself with MSpec for the :before event" do + MSpec.should_receive(:register).with(:before, @action) + @action.register + end + + it "registers itself with MSpec for the :after event" do + MSpec.should_receive(:register).with(:after, @action) + @action.register + end + + it "registers itself with MSpec for the :exception event" do + MSpec.should_receive(:register).with(:exception, @action) + @action.register + end + + it "registers itself with MSpec for the :finish event" do + MSpec.should_receive(:register).with(:finish, @action) + @action.register + end +end + +describe TagAction, "#unregister" do + before :each do + MSpec.stub(:unregister) + MSpec.stub(:read_tags).and_return([]) + @action = TagAction.new :add, :all, nil, nil, nil, nil + end + + it "unregisters itself with MSpec for the :before event" do + MSpec.should_receive(:unregister).with(:before, @action) + @action.unregister + end + + it "unregisters itself with MSpec for the :after event" do + MSpec.should_receive(:unregister).with(:after, @action) + @action.unregister + end + + it "unregisters itself with MSpec for the :exception event" do + MSpec.should_receive(:unregister).with(:exception, @action) + @action.unregister + end + + it "unregisters itself with MSpec for the :finish event" do + MSpec.should_receive(:unregister).with(:finish, @action) + @action.unregister + end +end diff --git a/spec/mspec/spec/runner/actions/taglist_spec.rb b/spec/mspec/spec/runner/actions/taglist_spec.rb new file mode 100644 index 0000000000..418c761c2d --- /dev/null +++ b/spec/mspec/spec/runner/actions/taglist_spec.rb @@ -0,0 +1,152 @@ +require File.dirname(__FILE__) + '/../../spec_helper' +require 'mspec/runner/actions/taglist' +require 'mspec/runner/mspec' +require 'mspec/runner/example' +require 'mspec/runner/tag' + +describe TagListAction, "#include?" do + it "returns true" do + TagListAction.new.include?(:anything).should be_true + end +end + +describe TagListAction, "#===" do + before :each do + tag = SpecTag.new "fails:description" + MSpec.stub(:read_tags).and_return([tag]) + @filter = double("MatchFilter").as_null_object + MatchFilter.stub(:new).and_return(@filter) + @action = TagListAction.new + @action.load + end + + it "returns true if filter === string returns true" do + @filter.should_receive(:===).with("str").and_return(true) + @action.===("str").should be_true + end + + it "returns false if filter === string returns false" do + @filter.should_receive(:===).with("str").and_return(false) + @action.===("str").should be_false + end +end + +describe TagListAction, "#start" do + before :each do + @stdout = $stdout + $stdout = IOStub.new + end + + after :each do + $stdout = @stdout + end + + it "prints a banner for specific tags" do + action = TagListAction.new ["fails", "unstable"] + action.start + $stdout.should == "\nListing specs tagged with 'fails', 'unstable'\n\n" + end + + it "prints a banner for all tags" do + action = TagListAction.new + action.start + $stdout.should == "\nListing all tagged specs\n\n" + end +end + +describe TagListAction, "#load" do + before :each do + @t1 = SpecTag.new "fails:I fail" + @t2 = SpecTag.new "unstable:I'm unstable" + end + + it "creates a MatchFilter for matching tags" do + MSpec.should_receive(:read_tags).with(["fails"]).and_return([@t1]) + MatchFilter.should_receive(:new).with(nil, "I fail") + TagListAction.new(["fails"]).load + end + + it "creates a MatchFilter for all tags" do + MSpec.should_receive(:read_tags).and_return([@t1, @t2]) + MatchFilter.should_receive(:new).with(nil, "I fail", "I'm unstable") + TagListAction.new.load + end + + it "does not create a MatchFilter if there are no matching tags" do + MSpec.stub(:read_tags).and_return([]) + MatchFilter.should_not_receive(:new) + TagListAction.new(["fails"]).load + end +end + +describe TagListAction, "#after" do + before :each do + @stdout = $stdout + $stdout = IOStub.new + + @state = double("ExampleState") + @state.stub(:description).and_return("str") + + @action = TagListAction.new + end + + after :each do + $stdout = @stdout + end + + it "prints nothing if the filter does not match" do + @action.should_receive(:===).with("str").and_return(false) + @action.after(@state) + $stdout.should == "" + end + + it "prints the example description if the filter matches" do + @action.should_receive(:===).with("str").and_return(true) + @action.after(@state) + $stdout.should == "str\n" + end +end + +describe TagListAction, "#register" do + before :each do + MSpec.stub(:register) + @action = TagListAction.new + end + + it "registers itself with MSpec for the :start event" do + MSpec.should_receive(:register).with(:start, @action) + @action.register + end + + it "registers itself with MSpec for the :load event" do + MSpec.should_receive(:register).with(:load, @action) + @action.register + end + + it "registers itself with MSpec for the :after event" do + MSpec.should_receive(:register).with(:after, @action) + @action.register + end +end + +describe TagListAction, "#unregister" do + before :each do + MSpec.stub(:unregister) + @action = TagListAction.new + end + + it "unregisters itself with MSpec for the :start event" do + MSpec.should_receive(:unregister).with(:start, @action) + @action.unregister + end + + it "unregisters itself with MSpec for the :load event" do + MSpec.should_receive(:unregister).with(:load, @action) + @action.unregister + end + + it "unregisters itself with MSpec for the :after event" do + MSpec.should_receive(:unregister).with(:after, @action) + @action.unregister + end +end diff --git a/spec/mspec/spec/runner/actions/tagpurge_spec.rb b/spec/mspec/spec/runner/actions/tagpurge_spec.rb new file mode 100644 index 0000000000..27ad2a1470 --- /dev/null +++ b/spec/mspec/spec/runner/actions/tagpurge_spec.rb @@ -0,0 +1,154 @@ +require File.dirname(__FILE__) + '/../../spec_helper' +require 'mspec/runner/actions/tagpurge' +require 'mspec/runner/mspec' +require 'mspec/runner/example' +require 'mspec/runner/tag' + +describe TagPurgeAction, "#start" do + before :each do + @stdout = $stdout + $stdout = IOStub.new + end + + after :each do + $stdout = @stdout + end + + it "prints a banner" do + action = TagPurgeAction.new + action.start + $stdout.should == "\nRemoving tags not matching any specs\n\n" + end +end + +describe TagPurgeAction, "#load" do + before :each do + @t1 = SpecTag.new "fails:I fail" + @t2 = SpecTag.new "unstable:I'm unstable" + end + + it "creates a MatchFilter for all tags" do + MSpec.should_receive(:read_tags).and_return([@t1, @t2]) + MatchFilter.should_receive(:new).with(nil, "I fail", "I'm unstable") + TagPurgeAction.new.load + end +end + +describe TagPurgeAction, "#after" do + before :each do + @state = double("ExampleState") + @state.stub(:description).and_return("str") + + @action = TagPurgeAction.new + end + + it "does not save the description if the filter does not match" do + @action.should_receive(:===).with("str").and_return(false) + @action.after @state + @action.matching.should == [] + end + + it "saves the description if the filter matches" do + @action.should_receive(:===).with("str").and_return(true) + @action.after @state + @action.matching.should == ["str"] + end +end + +describe TagPurgeAction, "#unload" do + before :each do + @stdout = $stdout + $stdout = IOStub.new + + @t1 = SpecTag.new "fails:I fail" + @t2 = SpecTag.new "unstable:I'm unstable" + @t3 = SpecTag.new "fails:I'm unstable" + + MSpec.stub(:read_tags).and_return([@t1, @t2, @t3]) + MSpec.stub(:write_tags) + + @state = double("ExampleState") + @state.stub(:description).and_return("I'm unstable") + + @action = TagPurgeAction.new + @action.load + @action.after @state + end + + after :each do + $stdout = @stdout + end + + it "does not rewrite any tags if there were no tags for the specs" do + MSpec.should_receive(:read_tags).and_return([]) + MSpec.should_receive(:delete_tags) + MSpec.should_not_receive(:write_tags) + + @action.load + @action.after @state + @action.unload + + $stdout.should == "" + end + + it "rewrites tags that were matched" do + MSpec.should_receive(:write_tags).with([@t2, @t3]) + @action.unload + end + + it "prints tags that were not matched" do + @action.unload + $stdout.should == "I fail\n" + end +end + +describe TagPurgeAction, "#unload" do + before :each do + @stdout = $stdout + $stdout = IOStub.new + + MSpec.stub(:read_tags).and_return([]) + + @state = double("ExampleState") + @state.stub(:description).and_return("I'm unstable") + + @action = TagPurgeAction.new + @action.load + @action.after @state + end + + after :each do + $stdout = @stdout + end + + it "deletes the tag file if no tags were found" do + MSpec.should_not_receive(:write_tags) + MSpec.should_receive(:delete_tags) + @action.unload + $stdout.should == "" + end +end + +describe TagPurgeAction, "#register" do + before :each do + MSpec.stub(:register) + @action = TagPurgeAction.new + end + + it "registers itself with MSpec for the :unload event" do + MSpec.should_receive(:register).with(:unload, @action) + @action.register + end +end + +describe TagPurgeAction, "#unregister" do + before :each do + MSpec.stub(:unregister) + @action = TagPurgeAction.new + end + + it "unregisters itself with MSpec for the :unload event" do + MSpec.should_receive(:unregister).with(:unload, @action) + @action.unregister + end +end diff --git a/spec/mspec/spec/runner/actions/tally_spec.rb b/spec/mspec/spec/runner/actions/tally_spec.rb new file mode 100644 index 0000000000..be4635ffeb --- /dev/null +++ b/spec/mspec/spec/runner/actions/tally_spec.rb @@ -0,0 +1,352 @@ +require File.dirname(__FILE__) + '/../../spec_helper' +require 'mspec/expectations/expectations' +require 'mspec/runner/actions/tally' +require 'mspec/runner/mspec' +require 'mspec/runner/example' + +describe Tally, "#files!" do + before :each do + @tally = Tally.new + end + + it "increments the count returned by #files" do + @tally.files! 3 + @tally.files.should == 3 + @tally.files! + @tally.files.should == 4 + end +end + +describe Tally, "#examples!" do + before :each do + @tally = Tally.new + end + + it "increments the count returned by #examples" do + @tally.examples! 2 + @tally.examples.should == 2 + @tally.examples! 2 + @tally.examples.should == 4 + end +end + +describe Tally, "#expectations!" do + before :each do + @tally = Tally.new + end + + it "increments the count returned by #expectations" do + @tally.expectations! + @tally.expectations.should == 1 + @tally.expectations! 3 + @tally.expectations.should == 4 + end +end + +describe Tally, "#failures!" do + before :each do + @tally = Tally.new + end + + it "increments the count returned by #failures" do + @tally.failures! 1 + @tally.failures.should == 1 + @tally.failures! + @tally.failures.should == 2 + end +end + +describe Tally, "#errors!" do + before :each do + @tally = Tally.new + end + + it "increments the count returned by #errors" do + @tally.errors! + @tally.errors.should == 1 + @tally.errors! 2 + @tally.errors.should == 3 + end +end + +describe Tally, "#guards!" do + before :each do + @tally = Tally.new + end + + it "increments the count returned by #guards" do + @tally.guards! + @tally.guards.should == 1 + @tally.guards! 2 + @tally.guards.should == 3 + end +end + +describe Tally, "#file" do + before :each do + @tally = Tally.new + end + + it "returns a formatted string of the number of #files" do + @tally.file.should == "0 files" + @tally.files! + @tally.file.should == "1 file" + @tally.files! + @tally.file.should == "2 files" + end +end + +describe Tally, "#example" do + before :each do + @tally = Tally.new + end + + it "returns a formatted string of the number of #examples" do + @tally.example.should == "0 examples" + @tally.examples! + @tally.example.should == "1 example" + @tally.examples! + @tally.example.should == "2 examples" + end +end + +describe Tally, "#expectation" do + before :each do + @tally = Tally.new + end + + it "returns a formatted string of the number of #expectations" do + @tally.expectation.should == "0 expectations" + @tally.expectations! + @tally.expectation.should == "1 expectation" + @tally.expectations! + @tally.expectation.should == "2 expectations" + end +end + +describe Tally, "#failure" do + before :each do + @tally = Tally.new + end + + it "returns a formatted string of the number of #failures" do + @tally.failure.should == "0 failures" + @tally.failures! + @tally.failure.should == "1 failure" + @tally.failures! + @tally.failure.should == "2 failures" + end +end + +describe Tally, "#error" do + before :each do + @tally = Tally.new + end + + it "returns a formatted string of the number of #errors" do + @tally.error.should == "0 errors" + @tally.errors! + @tally.error.should == "1 error" + @tally.errors! + @tally.error.should == "2 errors" + end +end + +describe Tally, "#guard" do + before :each do + @tally = Tally.new + end + + it "returns a formatted string of the number of #guards" do + @tally.guard.should == "0 guards" + @tally.guards! + @tally.guard.should == "1 guard" + @tally.guards! + @tally.guard.should == "2 guards" + end +end + +describe Tally, "#format" do + before :each do + @tally = Tally.new + end + + after :each do + MSpec.clear_modes + end + + it "returns a formatted string of counts" do + @tally.files! + @tally.examples! 2 + @tally.expectations! 4 + @tally.errors! + @tally.tagged! + @tally.format.should == "1 file, 2 examples, 4 expectations, 0 failures, 1 error, 1 tagged" + end + + it "includes guards if MSpec is in verify mode" do + MSpec.register_mode :verify + @tally.files! + @tally.examples! 2 + @tally.expectations! 4 + @tally.errors! + @tally.tagged! + @tally.guards! + @tally.format.should == + "1 file, 2 examples, 4 expectations, 0 failures, 1 error, 1 tagged, 1 guard" + end + + it "includes guards if MSpec is in report mode" do + MSpec.register_mode :report + @tally.files! + @tally.examples! 2 + @tally.expectations! 4 + @tally.errors! + @tally.tagged! + @tally.guards! 2 + @tally.format.should == + "1 file, 2 examples, 4 expectations, 0 failures, 1 error, 1 tagged, 2 guards" + end + + it "includes guards if MSpec is in report_on mode" do + MSpec.register_mode :report_on + @tally.files! + @tally.examples! 2 + @tally.expectations! 4 + @tally.errors! + @tally.guards! 2 + @tally.format.should == + "1 file, 2 examples, 4 expectations, 0 failures, 1 error, 0 tagged, 2 guards" + end +end + +describe TallyAction, "#counter" do + before :each do + @tally = TallyAction.new + @state = ExampleState.new("describe", "it") + end + + it "returns the Tally object" do + @tally.counter.should be_kind_of(Tally) + end +end + +describe TallyAction, "#load" do + before :each do + @tally = TallyAction.new + @state = ExampleState.new("describe", "it") + end + + it "increments the count returned by Tally#files" do + @tally.load + @tally.counter.files.should == 1 + end +end + +describe TallyAction, "#expectation" do + before :each do + @tally = TallyAction.new + @state = ExampleState.new("describe", "it") + end + + it "increments the count returned by Tally#expectations" do + @tally.expectation @state + @tally.counter.expectations.should == 1 + end +end + +describe TallyAction, "#example" do + before :each do + @tally = TallyAction.new + @state = ExampleState.new("describe", "it") + end + + it "increments counts returned by Tally#examples" do + @tally.example @state, nil + @tally.counter.examples.should == 1 + @tally.counter.expectations.should == 0 + @tally.counter.failures.should == 0 + @tally.counter.errors.should == 0 + end +end + +describe TallyAction, "#exception" do + before :each do + @tally = TallyAction.new + @state = ExampleState.new("describe", "it") + end + + it "increments counts returned by Tally#failures" do + exc = ExceptionState.new nil, nil, SpecExpectationNotMetError.new("Failed!") + @tally.exception exc + @tally.counter.examples.should == 0 + @tally.counter.expectations.should == 0 + @tally.counter.failures.should == 1 + @tally.counter.errors.should == 0 + end +end + +describe TallyAction, "#exception" do + before :each do + @tally = TallyAction.new + @state = ExampleState.new("describe", "it") + end + + it "increments counts returned by Tally#errors" do + exc = ExceptionState.new nil, nil, Exception.new("Error!") + @tally.exception exc + @tally.counter.examples.should == 0 + @tally.counter.expectations.should == 0 + @tally.counter.failures.should == 0 + @tally.counter.errors.should == 1 + end +end + +describe TallyAction, "#format" do + before :each do + @tally = TallyAction.new + @state = ExampleState.new("describe", "it") + end + + it "returns a readable string of counts" do + @tally.load + @tally.example @state, nil + @tally.expectation @state + @tally.expectation @state + exc = ExceptionState.new nil, nil, SpecExpectationNotMetError.new("Failed!") + @tally.exception exc + @tally.format.should == "1 file, 1 example, 2 expectations, 1 failure, 0 errors, 0 tagged" + end +end + +describe TallyAction, "#register" do + before :each do + @tally = TallyAction.new + @state = ExampleState.new("describe", "it") + end + + it "registers itself with MSpec for appropriate actions" do + MSpec.should_receive(:register).with(:load, @tally) + MSpec.should_receive(:register).with(:exception, @tally) + MSpec.should_receive(:register).with(:example, @tally) + MSpec.should_receive(:register).with(:tagged, @tally) + MSpec.should_receive(:register).with(:expectation, @tally) + @tally.register + end +end + +describe TallyAction, "#unregister" do + before :each do + @tally = TallyAction.new + @state = ExampleState.new("describe", "it") + end + + it "unregisters itself with MSpec for appropriate actions" do + MSpec.should_receive(:unregister).with(:load, @tally) + MSpec.should_receive(:unregister).with(:exception, @tally) + MSpec.should_receive(:unregister).with(:example, @tally) + MSpec.should_receive(:unregister).with(:tagged, @tally) + MSpec.should_receive(:unregister).with(:expectation, @tally) + @tally.unregister + end +end diff --git a/spec/mspec/spec/runner/actions/timer_spec.rb b/spec/mspec/spec/runner/actions/timer_spec.rb new file mode 100644 index 0000000000..417367d5a2 --- /dev/null +++ b/spec/mspec/spec/runner/actions/timer_spec.rb @@ -0,0 +1,44 @@ +require File.dirname(__FILE__) + '/../../spec_helper' +require 'mspec/runner/actions/timer' +require 'mspec/runner/mspec' +require 'time' + +describe TimerAction do + before :each do + @timer = TimerAction.new + @start_time = Time.utc(2009, 3, 30, 14, 5, 19) + @stop_time = Time.utc(2009, 3, 30, 14, 5, 52) + end + + it "responds to #start by recording the current time" do + Time.should_receive(:now) + @timer.start + end + + it "responds to #finish by recording the current time" do + Time.should_receive(:now) + @timer.finish + end + + it "responds to #elapsed by returning the difference between stop and start" do + Time.stub(:now).and_return(@start_time) + @timer.start + Time.stub(:now).and_return(@stop_time) + @timer.finish + @timer.elapsed.should == 33 + end + + it "responds to #format by returning a readable string of elapsed time" do + Time.stub(:now).and_return(@start_time) + @timer.start + Time.stub(:now).and_return(@stop_time) + @timer.finish + @timer.format.should == "Finished in 33.000000 seconds" + end + + it "responds to #register by registering itself with MSpec for appropriate actions" do + MSpec.should_receive(:register).with(:start, @timer) + MSpec.should_receive(:register).with(:finish, @timer) + @timer.register + end +end diff --git a/spec/mspec/spec/runner/context_spec.rb b/spec/mspec/spec/runner/context_spec.rb new file mode 100644 index 0000000000..f8759b639d --- /dev/null +++ b/spec/mspec/spec/runner/context_spec.rb @@ -0,0 +1,1041 @@ +require 'spec_helper' +require 'mspec/expectations/expectations' +require 'mspec/matchers/base' +require 'mspec/runner/mspec' +require 'mspec/mocks/mock' +require 'mspec/runner/context' +require 'mspec/runner/example' + +describe ContextState, "#describe" do + before :each do + @state = ContextState.new "C#m" + @proc = lambda {|*| ScratchPad.record :a } + ScratchPad.clear + end + + it "evaluates the passed block" do + @state.describe(&@proc) + ScratchPad.recorded.should == :a + end + + it "evaluates the passed block via #protect" do + @state.should_receive(:protect).with("C#m", @proc, false) + @state.describe(&@proc) + end + + it "registers #parent as the current MSpec ContextState" do + parent = ContextState.new "" + @state.parent = parent + MSpec.should_receive(:register_current).with(parent) + @state.describe { } + end + + it "registers self with MSpec when #shared? is true" do + state = ContextState.new "something shared", :shared => true + MSpec.should_receive(:register_shared).with(state) + state.describe { } + end +end + +describe ContextState, "#shared?" do + it "returns false when the ContextState is not shared" do + ContextState.new("").shared?.should be_false + end + + it "returns true when the ContextState is shared" do + ContextState.new("", {:shared => true}).shared?.should be_true + end +end + +describe ContextState, "#to_s" do + it "returns a description string for self when passed a Module" do + ContextState.new(Object).to_s.should == "Object" + end + + it "returns a description string for self when passed a String" do + ContextState.new("SomeClass").to_s.should == "SomeClass" + end + + it "returns a description string for self when passed a Module, String" do + ContextState.new(Object, "when empty").to_s.should == "Object when empty" + end + + it "returns a description string for self when passed a Module and String beginning with '#'" do + ContextState.new(Object, "#to_s").to_s.should == "Object#to_s" + end + + it "returns a description string for self when passed a Module and String beginning with '.'" do + ContextState.new(Object, ".to_s").to_s.should == "Object.to_s" + end + + it "returns a description string for self when passed a Module and String beginning with '::'" do + ContextState.new(Object, "::to_s").to_s.should == "Object::to_s" + end +end + +describe ContextState, "#description" do + before :each do + @state = ContextState.new "when empty" + @parent = ContextState.new "Toplevel" + end + + it "returns a composite description string from self and all parents" do + @parent.description.should == "Toplevel" + @state.description.should == "when empty" + @state.parent = @parent + @state.description.should == "Toplevel when empty" + end +end + +describe ContextState, "#it" do + before :each do + @state = ContextState.new "" + @proc = lambda {|*| } + + @ex = ExampleState.new("", "", &@proc) + end + + it "creates an ExampleState instance for the block" do + ExampleState.should_receive(:new).with(@state, "it", @proc).and_return(@ex) + @state.describe(&@proc) + @state.it("it", &@proc) + end + + it "calls registered :add actions" do + ExampleState.should_receive(:new).with(@state, "it", @proc).and_return(@ex) + + add_action = double("add") + add_action.should_receive(:add).with(@ex).and_return { ScratchPad.record :add } + MSpec.register :add, add_action + + @state.it("it", &@proc) + ScratchPad.recorded.should == :add + MSpec.unregister :add, add_action + end +end + +describe ContextState, "#examples" do + before :each do + @state = ContextState.new "" + end + + it "returns a list of all examples in this ContextState" do + @state.it("first") { } + @state.it("second") { } + @state.examples.size.should == 2 + end +end + +describe ContextState, "#before" do + before :each do + @state = ContextState.new "" + @proc = lambda {|*| } + end + + it "records the block for :each" do + @state.before(:each, &@proc) + @state.before(:each).should == [@proc] + end + + it "records the block for :all" do + @state.before(:all, &@proc) + @state.before(:all).should == [@proc] + end +end + +describe ContextState, "#after" do + before :each do + @state = ContextState.new "" + @proc = lambda {|*| } + end + + it "records the block for :each" do + @state.after(:each, &@proc) + @state.after(:each).should == [@proc] + end + + it "records the block for :all" do + @state.after(:all, &@proc) + @state.after(:all).should == [@proc] + end +end + +describe ContextState, "#pre" do + before :each do + @a = lambda {|*| } + @b = lambda {|*| } + @c = lambda {|*| } + + parent = ContextState.new "" + parent.before(:each, &@c) + parent.before(:all, &@c) + + @state = ContextState.new "" + @state.parent = parent + end + + it "returns before(:each) actions in the order they were defined" do + @state.before(:each, &@a) + @state.before(:each, &@b) + @state.pre(:each).should == [@c, @a, @b] + end + + it "returns before(:all) actions in the order they were defined" do + @state.before(:all, &@a) + @state.before(:all, &@b) + @state.pre(:all).should == [@c, @a, @b] + end +end + +describe ContextState, "#post" do + before :each do + @a = lambda {|*| } + @b = lambda {|*| } + @c = lambda {|*| } + + parent = ContextState.new "" + parent.after(:each, &@c) + parent.after(:all, &@c) + + @state = ContextState.new "" + @state.parent = parent + end + + it "returns after(:each) actions in the reverse order they were defined" do + @state.after(:each, &@a) + @state.after(:each, &@b) + @state.post(:each).should == [@b, @a, @c] + end + + it "returns after(:all) actions in the reverse order they were defined" do + @state.after(:all, &@a) + @state.after(:all, &@b) + @state.post(:all).should == [@b, @a, @c] + end +end + +describe ContextState, "#protect" do + before :each do + ScratchPad.record [] + @a = lambda {|*| ScratchPad << :a } + @b = lambda {|*| ScratchPad << :b } + @c = lambda {|*| raise Exception, "Fail!" } + end + + it "returns true and does execute any blocks if check and MSpec.mode?(:pretend) are true" do + MSpec.should_receive(:mode?).with(:pretend).and_return(true) + ContextState.new("").protect("message", [@a, @b]).should be_true + ScratchPad.recorded.should == [] + end + + it "executes the blocks if MSpec.mode?(:pretend) is false" do + MSpec.should_receive(:mode?).with(:pretend).and_return(false) + ContextState.new("").protect("message", [@a, @b]) + ScratchPad.recorded.should == [:a, :b] + end + + it "executes the blocks if check is false" do + ContextState.new("").protect("message", [@a, @b], false) + ScratchPad.recorded.should == [:a, :b] + end + + it "returns true if none of the blocks raise an exception" do + ContextState.new("").protect("message", [@a, @b]).should be_true + end + + it "returns false if any of the blocks raise an exception" do + ContextState.new("").protect("message", [@a, @c, @b]).should be_false + end +end + +describe ContextState, "#parent=" do + before :each do + @state = ContextState.new "" + @parent = double("describe") + @parent.stub(:parent).and_return(nil) + @parent.stub(:child) + end + + it "does not set self as a child of parent if shared" do + @parent.should_not_receive(:child) + state = ContextState.new "", :shared => true + state.parent = @parent + end + + it "does not set parents if shared" do + state = ContextState.new "", :shared => true + state.parent = @parent + state.parents.should == [state] + end + + it "sets self as a child of parent" do + @parent.should_receive(:child).with(@state) + @state.parent = @parent + end + + it "creates the list of parents" do + @state.parent = @parent + @state.parents.should == [@parent, @state] + end +end + +describe ContextState, "#parent" do + before :each do + @state = ContextState.new "" + @parent = double("describe") + @parent.stub(:parent).and_return(nil) + @parent.stub(:child) + end + + it "returns nil if parent has not been set" do + @state.parent.should be_nil + end + + it "returns the parent" do + @state.parent = @parent + @state.parent.should == @parent + end +end + +describe ContextState, "#parents" do + before :each do + @first = ContextState.new "" + @second = ContextState.new "" + @parent = double("describe") + @parent.stub(:parent).and_return(nil) + @parent.stub(:child) + end + + it "returns a list of all enclosing ContextState instances" do + @first.parent = @parent + @second.parent = @first + @second.parents.should == [@parent, @first, @second] + end +end + +describe ContextState, "#child" do + before :each do + @first = ContextState.new "" + @second = ContextState.new "" + @parent = double("describe") + @parent.stub(:parent).and_return(nil) + @parent.stub(:child) + end + + it "adds the ContextState to the list of contained ContextStates" do + @first.child @second + @first.children.should == [@second] + end +end + +describe ContextState, "#children" do + before :each do + @parent = ContextState.new "" + @first = ContextState.new "" + @second = ContextState.new "" + end + + it "returns the list of directly contained ContextStates" do + @first.parent = @parent + @second.parent = @first + @parent.children.should == [@first] + @first.children.should == [@second] + end +end + +describe ContextState, "#state" do + before :each do + MSpec.store :before, [] + MSpec.store :after, [] + + @state = ContextState.new "" + end + + it "returns nil if no spec is being executed" do + @state.state.should == nil + end + + it "returns a ExampleState instance if an example is being executed" do + ScratchPad.record @state + @state.describe { } + @state.it("") { ScratchPad.record ScratchPad.recorded.state } + @state.process + @state.state.should == nil + ScratchPad.recorded.should be_kind_of(ExampleState) + end +end + +describe ContextState, "#process" do + before :each do + MSpec.store :before, [] + MSpec.store :after, [] + MSpec.stub(:register_current) + + @state = ContextState.new "" + @state.describe { } + + @a = lambda {|*| ScratchPad << :a } + @b = lambda {|*| ScratchPad << :b } + ScratchPad.record [] + end + + it "calls each before(:all) block" do + @state.before(:all, &@a) + @state.before(:all, &@b) + @state.it("") { } + @state.process + ScratchPad.recorded.should == [:a, :b] + end + + it "calls each after(:all) block" do + @state.after(:all, &@a) + @state.after(:all, &@b) + @state.it("") { } + @state.process + ScratchPad.recorded.should == [:b, :a] + end + + it "calls each it block" do + @state.it("one", &@a) + @state.it("two", &@b) + @state.process + ScratchPad.recorded.should == [:a, :b] + end + + it "does not call the #it block if #filtered? returns true" do + @state.it("one", &@a) + @state.it("two", &@b) + @state.examples.first.stub(:filtered?).and_return(true) + @state.process + ScratchPad.recorded.should == [:b] + end + + it "calls each before(:each) block" do + @state.before(:each, &@a) + @state.before(:each, &@b) + @state.it("") { } + @state.process + ScratchPad.recorded.should == [:a, :b] + end + + it "calls each after(:each) block" do + @state.after(:each, &@a) + @state.after(:each, &@b) + @state.it("") { } + @state.process + ScratchPad.recorded.should == [:b, :a] + end + + it "calls Mock.cleanup for each it block" do + @state.it("") { } + @state.it("") { } + Mock.should_receive(:cleanup).twice + @state.process + end + + it "calls Mock.verify_count for each it block" do + @state.it("") { } + @state.it("") { } + Mock.should_receive(:verify_count).twice + @state.process + end + + it "calls the describe block" do + ScratchPad.record [] + @state.describe { ScratchPad << :a } + @state.process + ScratchPad.recorded.should == [:a] + end + + it "creates a new ExampleState instance for each example" do + ScratchPad.record @state + @state.describe { } + @state.it("it") { ScratchPad.record ScratchPad.recorded.state } + @state.process + ScratchPad.recorded.should be_kind_of(ExampleState) + end + + it "clears the expectations flag before evaluating the #it block" do + MSpec.clear_expectations + MSpec.should_receive(:clear_expectations) + @state.it("it") { ScratchPad.record MSpec.expectation? } + @state.process + ScratchPad.recorded.should be_false + end + + it "shuffles the spec list if MSpec.randomize? is true" do + MSpec.randomize + MSpec.should_receive(:shuffle) + @state.it("") { } + @state.process + MSpec.randomize false + end + + it "sets the current MSpec ContextState" do + MSpec.should_receive(:register_current).with(@state) + @state.process + end + + it "resets the current MSpec ContextState to nil when there are examples" do + MSpec.should_receive(:register_current).with(nil) + @state.it("") { } + @state.process + end + + it "resets the current MSpec ContextState to nil when there are no examples" do + MSpec.should_receive(:register_current).with(nil) + @state.process + end + + it "call #process on children when there are examples" do + child = ContextState.new "" + child.should_receive(:process) + @state.child child + @state.it("") { } + @state.process + end + + it "call #process on children when there are no examples" do + child = ContextState.new "" + child.should_receive(:process) + @state.child child + @state.process + end +end + +describe ContextState, "#process" do + before :each do + MSpec.store :exception, [] + + @state = ContextState.new "" + @state.describe { } + + action = double("action") + def action.exception(exc) + ScratchPad.record :exception if exc.exception.is_a? SpecExpectationNotFoundError + end + MSpec.register :exception, action + + MSpec.clear_expectations + ScratchPad.clear + end + + after :each do + MSpec.store :exception, nil + end + + it "raises an SpecExpectationNotFoundError if an #it block does not contain an expectation" do + @state.it("it") { } + @state.process + ScratchPad.recorded.should == :exception + end + + it "does not raise an SpecExpectationNotFoundError if an #it block does contain an expectation" do + @state.it("it") { MSpec.expectation } + @state.process + ScratchPad.recorded.should be_nil + end + + it "does not raise an SpecExpectationNotFoundError if the #it block causes a failure" do + @state.it("it") { raise Exception, "Failed!" } + @state.process + ScratchPad.recorded.should be_nil + end +end + +describe ContextState, "#process" do + before :each do + MSpec.store :example, [] + + @state = ContextState.new "" + @state.describe { } + + example = double("example") + def example.example(state, spec) + ScratchPad << state << spec + end + MSpec.register :example, example + + ScratchPad.record [] + end + + after :each do + MSpec.store :example, nil + end + + it "calls registered :example actions with the current ExampleState and block" do + @state.it("") { MSpec.expectation } + @state.process + + ScratchPad.recorded.first.should be_kind_of(ExampleState) + ScratchPad.recorded.last.should be_kind_of(Proc) + end + + it "does not call registered example actions if the example has no block" do + @state.it("empty example") + @state.process + ScratchPad.recorded.should == [] + end +end + +describe ContextState, "#process" do + before :each do + MSpec.store :before, [] + MSpec.store :after, [] + + @state = ContextState.new "" + @state.describe { } + @state.it("") { MSpec.expectation } + end + + after :each do + MSpec.store :before, nil + MSpec.store :after, nil + end + + it "calls registered :before actions with the current ExampleState instance" do + before = double("before") + before.should_receive(:before).and_return { + ScratchPad.record :before + @spec_state = @state.state + } + MSpec.register :before, before + @state.process + ScratchPad.recorded.should == :before + @spec_state.should be_kind_of(ExampleState) + end + + it "calls registered :after actions with the current ExampleState instance" do + after = double("after") + after.should_receive(:after).and_return { + ScratchPad.record :after + @spec_state = @state.state + } + MSpec.register :after, after + @state.process + ScratchPad.recorded.should == :after + @spec_state.should be_kind_of(ExampleState) + end +end + +describe ContextState, "#process" do + before :each do + MSpec.store :enter, [] + MSpec.store :leave, [] + + @state = ContextState.new "C#m" + @state.describe { } + @state.it("") { MSpec.expectation } + end + + after :each do + MSpec.store :enter, nil + MSpec.store :leave, nil + end + + it "calls registered :enter actions with the current #describe string" do + enter = double("enter") + enter.should_receive(:enter).with("C#m").and_return { ScratchPad.record :enter } + MSpec.register :enter, enter + @state.process + ScratchPad.recorded.should == :enter + end + + it "calls registered :leave actions" do + leave = double("leave") + leave.should_receive(:leave).and_return { ScratchPad.record :leave } + MSpec.register :leave, leave + @state.process + ScratchPad.recorded.should == :leave + end +end + +describe ContextState, "#process when an exception is raised in before(:all)" do + before :each do + MSpec.store :before, [] + MSpec.store :after, [] + + @state = ContextState.new "" + @state.describe { } + + @a = lambda {|*| ScratchPad << :a } + @b = lambda {|*| ScratchPad << :b } + ScratchPad.record [] + + @state.before(:all) { raise Exception, "Fail!" } + end + + after :each do + MSpec.store :before, nil + MSpec.store :after, nil + end + + it "does not call before(:each)" do + @state.before(:each, &@a) + @state.it("") { } + @state.process + ScratchPad.recorded.should == [] + end + + it "does not call the it block" do + @state.it("one", &@a) + @state.process + ScratchPad.recorded.should == [] + end + + it "does not call after(:each)" do + @state.after(:each, &@a) + @state.it("") { } + @state.process + ScratchPad.recorded.should == [] + end + + it "does not call after(:each)" do + @state.after(:all, &@a) + @state.it("") { } + @state.process + ScratchPad.recorded.should == [] + end + + it "does not call Mock.verify_count" do + @state.it("") { } + Mock.should_not_receive(:verify_count) + @state.process + end + + it "calls Mock.cleanup" do + @state.it("") { } + Mock.should_receive(:cleanup) + @state.process + end +end + +describe ContextState, "#process when an exception is raised in before(:each)" do + before :each do + MSpec.store :before, [] + MSpec.store :after, [] + + @state = ContextState.new "" + @state.describe { } + + @a = lambda {|*| ScratchPad << :a } + @b = lambda {|*| ScratchPad << :b } + ScratchPad.record [] + + @state.before(:each) { raise Exception, "Fail!" } + end + + after :each do + MSpec.store :before, nil + MSpec.store :after, nil + end + + it "does not call the it block" do + @state.it("one", &@a) + @state.process + ScratchPad.recorded.should == [] + end + + it "does call after(:each)" do + @state.after(:each, &@a) + @state.it("") { } + @state.process + ScratchPad.recorded.should == [:a] + end + + it "does not call Mock.verify_count" do + @state.it("") { } + Mock.should_not_receive(:verify_count) + @state.process + end +end + +describe ContextState, "#process in pretend mode" do + before :all do + MSpec.register_mode :pretend + end + + after :all do + MSpec.clear_modes + end + + before :each do + ScratchPad.clear + MSpec.store :before, [] + MSpec.store :after, [] + + @state = ContextState.new "" + @state.describe { } + @state.it("") { } + end + + after :each do + MSpec.store :before, nil + MSpec.store :after, nil + end + + it "calls registered :before actions with the current ExampleState instance" do + before = double("before") + before.should_receive(:before).and_return { + ScratchPad.record :before + @spec_state = @state.state + } + MSpec.register :before, before + @state.process + ScratchPad.recorded.should == :before + @spec_state.should be_kind_of(ExampleState) + end + + it "calls registered :after actions with the current ExampleState instance" do + after = double("after") + after.should_receive(:after).and_return { + ScratchPad.record :after + @spec_state = @state.state + } + MSpec.register :after, after + @state.process + ScratchPad.recorded.should == :after + @spec_state.should be_kind_of(ExampleState) + end +end + +describe ContextState, "#process in pretend mode" do + before :all do + MSpec.register_mode :pretend + end + + after :all do + MSpec.clear_modes + end + + before :each do + MSpec.store :before, [] + MSpec.store :after, [] + + @state = ContextState.new "" + @state.describe { } + + @a = lambda {|*| ScratchPad << :a } + @b = lambda {|*| ScratchPad << :b } + ScratchPad.record [] + end + + it "calls the describe block" do + ScratchPad.record [] + @state.describe { ScratchPad << :a } + @state.process + ScratchPad.recorded.should == [:a] + end + + it "does not call any before(:all) block" do + @state.before(:all, &@a) + @state.before(:all, &@b) + @state.it("") { } + @state.process + ScratchPad.recorded.should == [] + end + + it "does not call any after(:all) block" do + @state.after(:all, &@a) + @state.after(:all, &@b) + @state.it("") { } + @state.process + ScratchPad.recorded.should == [] + end + + it "does not call any it block" do + @state.it("one", &@a) + @state.it("two", &@b) + @state.process + ScratchPad.recorded.should == [] + end + + it "does not call any before(:each) block" do + @state.before(:each, &@a) + @state.before(:each, &@b) + @state.it("") { } + @state.process + ScratchPad.recorded.should == [] + end + + it "does not call any after(:each) block" do + @state.after(:each, &@a) + @state.after(:each, &@b) + @state.it("") { } + @state.process + ScratchPad.recorded.should == [] + end + + it "does not call Mock.cleanup" do + @state.it("") { } + @state.it("") { } + Mock.should_not_receive(:cleanup) + @state.process + end +end + +describe ContextState, "#process in pretend mode" do + before :all do + MSpec.register_mode :pretend + end + + after :all do + MSpec.clear_modes + end + + before :each do + MSpec.store :enter, [] + MSpec.store :leave, [] + + @state = ContextState.new "" + @state.describe { } + @state.it("") { } + end + + after :each do + MSpec.store :enter, nil + MSpec.store :leave, nil + end + + it "calls registered :enter actions with the current #describe string" do + enter = double("enter") + enter.should_receive(:enter).and_return { ScratchPad.record :enter } + MSpec.register :enter, enter + @state.process + ScratchPad.recorded.should == :enter + end + + it "calls registered :leave actions" do + leave = double("leave") + leave.should_receive(:leave).and_return { ScratchPad.record :leave } + MSpec.register :leave, leave + @state.process + ScratchPad.recorded.should == :leave + end +end + +describe ContextState, "#it_should_behave_like" do + before :each do + @shared_desc = :shared_context + @shared = ContextState.new(@shared_desc, :shared => true) + MSpec.stub(:retrieve_shared).and_return(@shared) + + @state = ContextState.new "Top level" + @a = lambda {|*| } + @b = lambda {|*| } + end + + it "raises an Exception if unable to find the shared ContextState" do + MSpec.should_receive(:retrieve_shared).and_return(nil) + lambda { @state.it_should_behave_like "this" }.should raise_error(Exception) + end + + describe "for nested ContextState instances" do + before :each do + @nested = ContextState.new "nested context" + @nested.parents.unshift @shared + + @shared.children << @nested + + @nested_dup = @nested.dup + @nested.stub(:dup).and_return(@nested_dup) + end + + it "duplicates the nested ContextState" do + @state.it_should_behave_like @shared_desc + @state.children.first.should equal(@nested_dup) + end + + it "sets the parent of the nested ContextState to the containing ContextState" do + @state.it_should_behave_like @shared_desc + @nested_dup.parent.should equal(@state) + end + + it "sets the context for nested examples to the nested ContextState's dup" do + @shared.it "an example", &@a + @shared.it "another example", &@b + @state.it_should_behave_like @shared_desc + @nested_dup.examples.each { |x| x.context.should equal(@nested_dup) } + end + + it "omits the shored ContextState's description" do + @nested.it "an example", &@a + @nested.it "another example", &@b + @state.it_should_behave_like @shared_desc + + @nested_dup.description.should == "Top level nested context" + @nested_dup.examples.first.description.should == "Top level nested context an example" + @nested_dup.examples.last.description.should == "Top level nested context another example" + end + end + + it "adds duped examples from the shared ContextState" do + @shared.it "some method", &@a + ex_dup = @shared.examples.first.dup + @shared.examples.first.stub(:dup).and_return(ex_dup) + + @state.it_should_behave_like @shared_desc + @state.examples.should == [ex_dup] + end + + it "sets the context for examples to the containing ContextState" do + @shared.it "an example", &@a + @shared.it "another example", &@b + @state.it_should_behave_like @shared_desc + @state.examples.each { |x| x.context.should equal(@state) } + end + + it "adds before(:all) blocks from the shared ContextState" do + @shared.before :all, &@a + @shared.before :all, &@b + @state.it_should_behave_like @shared_desc + @state.before(:all).should include(*@shared.before(:all)) + end + + it "adds before(:each) blocks from the shared ContextState" do + @shared.before :each, &@a + @shared.before :each, &@b + @state.it_should_behave_like @shared_desc + @state.before(:each).should include(*@shared.before(:each)) + end + + it "adds after(:each) blocks from the shared ContextState" do + @shared.after :each, &@a + @shared.after :each, &@b + @state.it_should_behave_like @shared_desc + @state.after(:each).should include(*@shared.after(:each)) + end + + it "adds after(:all) blocks from the shared ContextState" do + @shared.after :all, &@a + @shared.after :all, &@b + @state.it_should_behave_like @shared_desc + @state.after(:all).should include(*@shared.after(:all)) + end +end + +describe ContextState, "#filter_examples" do + before :each do + @state = ContextState.new "" + @state.it("one") { } + @state.it("two") { } + end + + it "removes examples that are filtered" do + @state.examples.first.stub(:filtered?).and_return(true) + @state.examples.size.should == 2 + @state.filter_examples + @state.examples.size.should == 1 + end + + it "returns true if there are remaining examples to evaluate" do + @state.examples.first.stub(:filtered?).and_return(true) + @state.filter_examples.should be_true + end + + it "returns false if there are no remaining examples to evaluate" do + @state.examples.first.stub(:filtered?).and_return(true) + @state.examples.last.stub(:filtered?).and_return(true) + @state.filter_examples.should be_false + end +end diff --git a/spec/mspec/spec/runner/example_spec.rb b/spec/mspec/spec/runner/example_spec.rb new file mode 100644 index 0000000000..b4391f802d --- /dev/null +++ b/spec/mspec/spec/runner/example_spec.rb @@ -0,0 +1,117 @@ +require 'spec_helper' +require 'mspec/matchers/base' +require 'mspec/runner/mspec' +require 'mspec/mocks/mock' +require 'mspec/runner/example' + +describe ExampleState do + it "is initialized with the ContextState, #it string, and #it block" do + prc = lambda { } + context = ContextState.new "" + ExampleState.new(context, "does", prc).should be_kind_of(ExampleState) + end +end + +describe ExampleState, "#describe" do + before :each do + @context = ContextState.new Object, "#to_s" + @state = ExampleState.new @context, "it" + end + + it "returns the ContextState#description" do + @state.describe.should == @context.description + end +end + +describe ExampleState, "#it" do + before :each do + @state = ExampleState.new ContextState.new("describe"), "it" + end + + it "returns the argument to the #it block" do + @state.it.should == "it" + end +end + +describe ExampleState, "#context=" do + before :each do + @state = ExampleState.new ContextState.new("describe"), "it" + @context = ContextState.new "New#context" + end + + it "sets the containing ContextState" do + @state.context = @context + @state.context.should == @context + end + + it "resets the description" do + @state.description.should == "describe it" + @state.context = @context + @state.description.should == "New#context it" + end +end + +describe ExampleState, "#example" do + before :each do + @proc = lambda { } + @state = ExampleState.new ContextState.new("describe"), "it", @proc + end + + it "returns the #it block" do + @state.example.should == @proc + end +end + +describe ExampleState, "#filtered?" do + before :each do + MSpec.store :include, nil + MSpec.store :exclude, nil + + @state = ExampleState.new ContextState.new("describe"), "it" + @filter = double("filter") + end + + after :each do + MSpec.store :include, nil + MSpec.store :exclude, nil + end + + it "returns false if MSpec include filters list is empty" do + @state.filtered?.should == false + end + + it "returns false if MSpec include filters match this spec" do + @filter.should_receive(:===).and_return(true) + MSpec.register :include, @filter + @state.filtered?.should == false + end + + it "returns true if MSpec include filters do not match this spec" do + @filter.should_receive(:===).and_return(false) + MSpec.register :include, @filter + @state.filtered?.should == true + end + + it "returns false if MSpec exclude filters list is empty" do + @state.filtered?.should == false + end + + it "returns false if MSpec exclude filters do not match this spec" do + @filter.should_receive(:===).and_return(false) + MSpec.register :exclude, @filter + @state.filtered?.should == false + end + + it "returns true if MSpec exclude filters match this spec" do + @filter.should_receive(:===).and_return(true) + MSpec.register :exclude, @filter + @state.filtered?.should == true + end + + it "returns true if MSpec include and exclude filters match this spec" do + @filter.should_receive(:===).twice.and_return(true) + MSpec.register :include, @filter + MSpec.register :exclude, @filter + @state.filtered?.should == true + end +end diff --git a/spec/mspec/spec/runner/exception_spec.rb b/spec/mspec/spec/runner/exception_spec.rb new file mode 100644 index 0000000000..309442435c --- /dev/null +++ b/spec/mspec/spec/runner/exception_spec.rb @@ -0,0 +1,146 @@ +require 'spec_helper' +require 'mspec/expectations/expectations' +require 'mspec/runner/example' +require 'mspec/runner/exception' +require 'mspec/utils/script' + +describe ExceptionState, "#initialize" do + it "takes a state, location (e.g. before :each), and exception" do + context = ContextState.new "Class#method" + state = ExampleState.new context, "does something" + exc = Exception.new "Fail!" + ExceptionState.new(state, "location", exc).should be_kind_of(ExceptionState) + end +end + +describe ExceptionState, "#description" do + before :each do + context = ContextState.new "Class#method" + @state = ExampleState.new context, "does something" + end + + it "returns the state description if state was not nil" do + exc = ExceptionState.new(@state, nil, nil) + exc.description.should == "Class#method does something" + end + + it "returns the location if it is not nil and description is nil" do + exc = ExceptionState.new(nil, "location", nil) + exc.description.should == "An exception occurred during: location" + end + + it "returns both description and location if neither are nil" do + exc = ExceptionState.new(@state, "location", nil) + exc.description.should == "An exception occurred during: location\nClass#method does something" + end +end + +describe ExceptionState, "#describe" do + before :each do + context = ContextState.new "Class#method" + @state = ExampleState.new context, "does something" + end + + it "returns the ExampleState#describe string if created with a non-nil state" do + ExceptionState.new(@state, nil, nil).describe.should == @state.describe + end + + it "returns an empty string if created with a nil state" do + ExceptionState.new(nil, nil, nil).describe.should == "" + end +end + +describe ExceptionState, "#it" do + before :each do + context = ContextState.new "Class#method" + @state = ExampleState.new context, "does something" + end + + it "returns the ExampleState#it string if created with a non-nil state" do + ExceptionState.new(@state, nil, nil).it.should == @state.it + end + + it "returns an empty string if created with a nil state" do + ExceptionState.new(nil, nil, nil).it.should == "" + end +end + +describe ExceptionState, "#failure?" do + before :each do + @state = ExampleState.new ContextState.new("C#m"), "works" + end + + it "returns true if the exception is an SpecExpectationNotMetError" do + exc = ExceptionState.new @state, "", SpecExpectationNotMetError.new("Fail!") + exc.failure?.should be_true + end + + it "returns true if the exception is an SpecExpectationNotFoundError" do + exc = ExceptionState.new @state, "", SpecExpectationNotFoundError.new("Fail!") + exc.failure?.should be_true + end + + it "returns false if the exception is not an SpecExpectationNotMetError or an SpecExpectationNotFoundError" do + exc = ExceptionState.new @state, "", Exception.new("Fail!") + exc.failure?.should be_false + end +end + +describe ExceptionState, "#message" do + before :each do + @state = ExampleState.new ContextState.new("C#m"), "works" + end + + it "returns if the exception message is empty" do + exc = ExceptionState.new @state, "", Exception.new("") + exc.message.should == "" + end + + it "returns the message without exception class when the exception is an SpecExpectationNotMetError" do + exc = ExceptionState.new @state, "", SpecExpectationNotMetError.new("Fail!") + exc.message.should == "Fail!" + end + + it "returns SpecExpectationNotFoundError#message when the exception is an SpecExpectationNotFoundError" do + e = SpecExpectationNotFoundError.new + exc = ExceptionState.new @state, "", e + exc.message.should == e.message + end + + it "returns the message with exception class when the exception is not an SpecExpectationNotMetError or an SpecExpectationNotFoundError" do + exc = ExceptionState.new @state, "", Exception.new("Fail!") + exc.message.should == "Exception: Fail!" + end +end + +describe ExceptionState, "#backtrace" do + before :each do + @state = ExampleState.new ContextState.new("C#m"), "works" + begin + raise Exception + rescue Exception => @exception + @exc = ExceptionState.new @state, "", @exception + end + end + + after :each do + $MSPEC_DEBUG = nil + end + + it "returns a string representation of the exception backtrace" do + @exc.backtrace.should be_kind_of(String) + end + + it "does not filter files from the backtrace if $MSPEC_DEBUG is true" do + $MSPEC_DEBUG = true + @exc.backtrace.should == @exception.backtrace.join("\n") + end + + it "filters files matching config[:backtrace_filter]" do + MSpecScript.set :backtrace_filter, %r[mspec/lib] + $MSPEC_DEBUG = nil + @exc.backtrace.split("\n").each do |line| + line.should_not =~ %r[mspec/lib] + end + end +end diff --git a/spec/mspec/spec/runner/filters/a.yaml b/spec/mspec/spec/runner/filters/a.yaml new file mode 100644 index 0000000000..1940e3cba6 --- /dev/null +++ b/spec/mspec/spec/runner/filters/a.yaml @@ -0,0 +1,4 @@ +--- +A#: +- a +- aa diff --git a/spec/mspec/spec/runner/filters/b.yaml b/spec/mspec/spec/runner/filters/b.yaml new file mode 100644 index 0000000000..a24bdb2f19 --- /dev/null +++ b/spec/mspec/spec/runner/filters/b.yaml @@ -0,0 +1,11 @@ +--- +B.: +- b +- bb +B::C#: +- b! +- b= +- b? +- "-" +- "[]" +- "[]=" diff --git a/spec/mspec/spec/runner/filters/match_spec.rb b/spec/mspec/spec/runner/filters/match_spec.rb new file mode 100644 index 0000000000..f2c665c495 --- /dev/null +++ b/spec/mspec/spec/runner/filters/match_spec.rb @@ -0,0 +1,34 @@ +require File.dirname(__FILE__) + '/../../spec_helper' +require 'mspec/runner/mspec' +require 'mspec/runner/filters/match' + +describe MatchFilter, "#===" do + before :each do + @filter = MatchFilter.new nil, 'a', 'b', 'c' + end + + it "returns true if the argument matches any of the #initialize strings" do + @filter.===('aaa').should == true + @filter.===('bccb').should == true + end + + it "returns false if the argument matches none of the #initialize strings" do + @filter.===('d').should == false + end +end + +describe MatchFilter, "#register" do + it "registers itself with MSpec for the designated action list" do + filter = MatchFilter.new :include + MSpec.should_receive(:register).with(:include, filter) + filter.register + end +end + +describe MatchFilter, "#unregister" do + it "unregisters itself with MSpec for the designated action list" do + filter = MatchFilter.new :exclude + MSpec.should_receive(:unregister).with(:exclude, filter) + filter.unregister + end +end diff --git a/spec/mspec/spec/runner/filters/profile_spec.rb b/spec/mspec/spec/runner/filters/profile_spec.rb new file mode 100644 index 0000000000..78807bca5c --- /dev/null +++ b/spec/mspec/spec/runner/filters/profile_spec.rb @@ -0,0 +1,117 @@ +require File.dirname(__FILE__) + '/../../spec_helper' +require 'mspec/runner/mspec' +require 'mspec/runner/filters/profile' + +describe ProfileFilter, "#find" do + before :each do + @filter = ProfileFilter.new nil + File.stub(:exist?).and_return(false) + @file = "rails.yaml" + end + + it "attempts to locate the file through the expanded path name" do + File.should_receive(:expand_path).with(@file).and_return(@file) + File.should_receive(:exist?).with(@file).and_return(true) + @filter.find(@file).should == @file + end + + it "attemps to locate the file in 'spec/profiles'" do + path = File.join "spec/profiles", @file + File.should_receive(:exist?).with(path).and_return(true) + @filter.find(@file).should == path + end + + it "attemps to locate the file in 'spec'" do + path = File.join "spec", @file + File.should_receive(:exist?).with(path).and_return(true) + @filter.find(@file).should == path + end + + it "attemps to locate the file in 'profiles'" do + path = File.join "profiles", @file + File.should_receive(:exist?).with(path).and_return(true) + @filter.find(@file).should == path + end + + it "attemps to locate the file in '.'" do + path = File.join ".", @file + File.should_receive(:exist?).with(path).and_return(true) + @filter.find(@file).should == path + end +end + +describe ProfileFilter, "#parse" do + before :each do + @filter = ProfileFilter.new nil + @file = File.open(File.dirname(__FILE__) + "/b.yaml", "r") + end + + after :each do + @file.close + end + + it "creates a Hash of the contents of the YAML file" do + @filter.parse(@file).should == { + "B." => ["b", "bb"], + "B::C#" => ["b!", "b=", "b?", "-", "[]", "[]="] + } + end +end + +describe ProfileFilter, "#load" do + before :each do + @filter = ProfileFilter.new nil + @files = [ + File.dirname(__FILE__) + "/a.yaml", + File.dirname(__FILE__) + "/b.yaml" + ] + end + + it "generates a composite hash from multiple YAML files" do + @filter.load(*@files).should == { + "A#" => ["a", "aa"], + "B." => ["b", "bb"], + "B::C#" => ["b!", "b=", "b?", "-", "[]", "[]="] + } + end +end + +describe ProfileFilter, "#===" do + before :each do + @filter = ProfileFilter.new nil + @filter.stub(:load).and_return({ "A#" => ["[]=", "a", "a!", "a?", "aa="]}) + @filter.send :initialize, nil + end + + it "returns true if the spec description is for a method in the profile" do + @filter.===("The A#[]= method").should == true + @filter.===("A#a returns").should == true + @filter.===("A#a! replaces").should == true + @filter.===("A#a? returns").should == true + @filter.===("A#aa= raises").should == true + end + + it "returns false if the spec description is for a method not in the profile" do + @filter.===("The A#[] method").should == false + @filter.===("B#a returns").should == false + @filter.===("A.a! replaces").should == false + @filter.===("AA#a? returns").should == false + @filter.===("A#aa raises").should == false + end +end + +describe ProfileFilter, "#register" do + it "registers itself with MSpec for the designated action list" do + filter = ProfileFilter.new :include + MSpec.should_receive(:register).with(:include, filter) + filter.register + end +end + +describe ProfileFilter, "#unregister" do + it "unregisters itself with MSpec for the designated action list" do + filter = ProfileFilter.new :exclude + MSpec.should_receive(:unregister).with(:exclude, filter) + filter.unregister + end +end diff --git a/spec/mspec/spec/runner/filters/regexp_spec.rb b/spec/mspec/spec/runner/filters/regexp_spec.rb new file mode 100644 index 0000000000..6c05b0f42f --- /dev/null +++ b/spec/mspec/spec/runner/filters/regexp_spec.rb @@ -0,0 +1,13 @@ +require File.dirname(__FILE__) + '/../../spec_helper' +require 'mspec/runner/mspec' +require 'mspec/runner/filters/regexp' + +describe RegexpFilter, "#to_regexp" do + before :each do + @filter = RegexpFilter.new nil + end + + it "converts its arguments to Regexp instances" do + @filter.to_regexp('a(b|c)', 'b[^ab]', 'cc?').should == [/a(b|c)/, /b[^ab]/, /cc?/] + end +end diff --git a/spec/mspec/spec/runner/filters/tag_spec.rb b/spec/mspec/spec/runner/filters/tag_spec.rb new file mode 100644 index 0000000000..fe1f3df039 --- /dev/null +++ b/spec/mspec/spec/runner/filters/tag_spec.rb @@ -0,0 +1,92 @@ +require File.dirname(__FILE__) + '/../../spec_helper' +require 'mspec/runner/mspec' +require 'mspec/runner/filters/match' +require 'mspec/runner/filters/tag' + +describe TagFilter, "#load" do + before :each do + @match = double("match filter").as_null_object + @filter = TagFilter.new :include, "tag", "key" + @tag = SpecTag.new "tag(comment):description" + MSpec.stub(:read_tags).and_return([@tag]) + MSpec.stub(:register) + end + + it "loads tags from the tag file" do + MSpec.should_receive(:read_tags).with(["tag", "key"]).and_return([]) + @filter.load + end + + + it "registers itself with MSpec for the :include action" do + filter = TagFilter.new(:include) + MSpec.should_receive(:register).with(:include, filter) + filter.load + end + + it "registers itself with MSpec for the :exclude action" do + filter = TagFilter.new(:exclude) + MSpec.should_receive(:register).with(:exclude, filter) + filter.load + end +end + +describe TagFilter, "#unload" do + before :each do + @filter = TagFilter.new :include, "tag", "key" + @tag = SpecTag.new "tag(comment):description" + MSpec.stub(:read_tags).and_return([@tag]) + MSpec.stub(:register) + end + + it "unregisters itself" do + @filter.load + MSpec.should_receive(:unregister).with(:include, @filter) + @filter.unload + end +end + +describe TagFilter, "#register" do + before :each do + MSpec.stub(:register) + end + + it "registers itself with MSpec for the :load, :unload actions" do + filter = TagFilter.new(nil) + MSpec.should_receive(:register).with(:load, filter) + MSpec.should_receive(:register).with(:unload, filter) + filter.register + end +end + +describe TagFilter, "#unregister" do + before :each do + MSpec.stub(:unregister) + end + + it "unregisters itself with MSpec for the :load, :unload actions" do + filter = TagFilter.new(nil) + MSpec.should_receive(:unregister).with(:load, filter) + MSpec.should_receive(:unregister).with(:unload, filter) + filter.unregister + end +end + +describe TagFilter, "#===" do + before :each do + @filter = TagFilter.new nil, "tag", "key" + @tag = SpecTag.new "tag(comment):description" + MSpec.stub(:read_tags).and_return([@tag]) + MSpec.stub(:register) + @filter.load + end + + it "returns true if the argument matches any of the descriptions" do + @filter.===('description').should == true + end + + it "returns false if the argument matches none of the descriptions" do + @filter.===('descriptionA').should == false + @filter.===('adescription').should == false + end +end diff --git a/spec/mspec/spec/runner/formatters/describe_spec.rb b/spec/mspec/spec/runner/formatters/describe_spec.rb new file mode 100644 index 0000000000..415ced71fb --- /dev/null +++ b/spec/mspec/spec/runner/formatters/describe_spec.rb @@ -0,0 +1,67 @@ +require File.dirname(__FILE__) + '/../../spec_helper' +require 'mspec/runner/formatters/describe' +require 'mspec/runner/example' + +describe DescribeFormatter, "#finish" do + before :each do + MSpec.stub(:register) + MSpec.stub(:unregister) + + @timer = double("timer").as_null_object + TimerAction.stub(:new).and_return(@timer) + @timer.stub(:format).and_return("Finished in 2.0 seconds") + + $stdout = @out = IOStub.new + context = ContextState.new "Class#method" + @state = ExampleState.new(context, "runs") + + @formatter = DescribeFormatter.new + @formatter.register + + @tally = @formatter.tally + @counter = @tally.counter + + @counter.files! + @counter.examples! + @counter.expectations! 2 + end + + after :each do + $stdout = STDOUT + end + + it "prints a summary of elapsed time" do + @formatter.finish + @out.should =~ /^Finished in 2.0 seconds$/ + end + + it "prints a tally of counts" do + @formatter.finish + @out.should =~ /^1 file, 1 example, 2 expectations, 0 failures, 0 errors, 0 tagged$/ + end + + it "does not print exceptions" do + @formatter.finish + @out.should == %[ + +Finished in 2.0 seconds + +1 file, 1 example, 2 expectations, 0 failures, 0 errors, 0 tagged +] + end + + it "prints a summary of failures and errors for each describe block" do + exc = ExceptionState.new @state, nil, MSpecExampleError.new("broken") + exc.stub(:backtrace).and_return("path/to/some/file.rb:35:in method") + @formatter.exception exc + @formatter.finish + @out.should == %[ + +Class#method 0 failures, 1 error + +Finished in 2.0 seconds + +1 file, 1 example, 2 expectations, 0 failures, 0 errors, 0 tagged +] + end +end diff --git a/spec/mspec/spec/runner/formatters/dotted_spec.rb b/spec/mspec/spec/runner/formatters/dotted_spec.rb new file mode 100644 index 0000000000..1e9b06f6e1 --- /dev/null +++ b/spec/mspec/spec/runner/formatters/dotted_spec.rb @@ -0,0 +1,285 @@ +require File.dirname(__FILE__) + '/../../spec_helper' +require 'mspec/runner/formatters/dotted' +require 'mspec/runner/mspec' +require 'mspec/runner/example' +require 'mspec/utils/script' + +describe DottedFormatter, "#initialize" do + it "permits zero arguments" do + DottedFormatter.new + end + + it "accepts one argument" do + DottedFormatter.new nil + end +end + +describe DottedFormatter, "#register" do + before :each do + @formatter = DottedFormatter.new + MSpec.stub(:register) + end + + it "registers self with MSpec for appropriate actions" do + MSpec.should_receive(:register).with(:exception, @formatter) + MSpec.should_receive(:register).with(:before, @formatter) + MSpec.should_receive(:register).with(:after, @formatter) + MSpec.should_receive(:register).with(:finish, @formatter) + @formatter.register + end + + it "creates TimerAction and TallyAction" do + timer = double("timer") + tally = double("tally") + timer.should_receive(:register) + tally.should_receive(:register) + tally.should_receive(:counter) + TimerAction.should_receive(:new).and_return(timer) + TallyAction.should_receive(:new).and_return(tally) + @formatter.register + end +end + +describe DottedFormatter, "#print" do + before :each do + $stdout = IOStub.new + end + + after :each do + $stdout = STDOUT + end + + it "writes to $stdout by default" do + formatter = DottedFormatter.new + formatter.print "begonias" + $stdout.should == "begonias" + end + + it "writes to the file specified when the formatter was created" do + out = IOStub.new + File.should_receive(:open).with("some/file", "w").and_return(out) + formatter = DottedFormatter.new "some/file" + formatter.print "begonias" + out.should == "begonias" + end + + it "flushes the IO output" do + $stdout.should_receive(:flush) + formatter = DottedFormatter.new + formatter.print "begonias" + end +end + +describe DottedFormatter, "#exception" do + before :each do + @formatter = DottedFormatter.new + @failure = ExceptionState.new nil, nil, SpecExpectationNotMetError.new("failed") + @error = ExceptionState.new nil, nil, MSpecExampleError.new("boom!") + end + + it "sets the #failure? flag" do + @formatter.exception @failure + @formatter.failure?.should be_true + @formatter.exception @error + @formatter.failure?.should be_false + end + + it "sets the #exception? flag" do + @formatter.exception @error + @formatter.exception?.should be_true + @formatter.exception @failure + @formatter.exception?.should be_true + end + + it "addes the exception to the list of exceptions" do + @formatter.exceptions.should == [] + @formatter.exception @error + @formatter.exception @failure + @formatter.exceptions.should == [@error, @failure] + end +end + +describe DottedFormatter, "#exception?" do + before :each do + @formatter = DottedFormatter.new + @failure = ExceptionState.new nil, nil, SpecExpectationNotMetError.new("failed") + @error = ExceptionState.new nil, nil, MSpecExampleError.new("boom!") + end + + it "returns false if there have been no exceptions" do + @formatter.exception?.should be_false + end + + it "returns true if any exceptions are errors" do + @formatter.exception @failure + @formatter.exception @error + @formatter.exception?.should be_true + end + + it "returns true if all exceptions are failures" do + @formatter.exception @failure + @formatter.exception @failure + @formatter.exception?.should be_true + end + + it "returns true if all exceptions are errors" do + @formatter.exception @error + @formatter.exception @error + @formatter.exception?.should be_true + end +end + +describe DottedFormatter, "#failure?" do + before :each do + @formatter = DottedFormatter.new + @failure = ExceptionState.new nil, nil, SpecExpectationNotMetError.new("failed") + @error = ExceptionState.new nil, nil, MSpecExampleError.new("boom!") + end + + it "returns false if there have been no exceptions" do + @formatter.failure?.should be_false + end + + it "returns false if any exceptions are errors" do + @formatter.exception @failure + @formatter.exception @error + @formatter.failure?.should be_false + end + + it "returns true if all exceptions are failures" do + @formatter.exception @failure + @formatter.exception @failure + @formatter.failure?.should be_true + end +end + +describe DottedFormatter, "#before" do + before :each do + @state = ExampleState.new ContextState.new("describe"), "it" + @formatter = DottedFormatter.new + @formatter.exception ExceptionState.new(nil, nil, SpecExpectationNotMetError.new("Failed!")) + end + + it "resets the #failure? flag to false" do + @formatter.failure?.should be_true + @formatter.before @state + @formatter.failure?.should be_false + end + + it "resets the #exception? flag to false" do + @formatter.exception?.should be_true + @formatter.before @state + @formatter.exception?.should be_false + end +end + +describe DottedFormatter, "#after" do + before :each do + $stdout = @out = IOStub.new + @formatter = DottedFormatter.new + @state = ExampleState.new ContextState.new("describe"), "it" + end + + after :each do + $stdout = STDOUT + end + + it "prints a '.' if there was no exception raised" do + @formatter.after(@state) + @out.should == "." + end + + it "prints an 'F' if there was an expectation failure" do + exc = SpecExpectationNotMetError.new "failed" + @formatter.exception ExceptionState.new(@state, nil, exc) + @formatter.after(@state) + @out.should == "F" + end + + it "prints an 'E' if there was an exception other than expectation failure" do + exc = MSpecExampleError.new("boom!") + @formatter.exception ExceptionState.new(@state, nil, exc) + @formatter.after(@state) + @out.should == "E" + end + + it "prints an 'E' if there are mixed exceptions and exepctation failures" do + exc = SpecExpectationNotMetError.new "failed" + @formatter.exception ExceptionState.new(@state, nil, exc) + exc = MSpecExampleError.new("boom!") + @formatter.exception ExceptionState.new(@state, nil, exc) + @formatter.after(@state) + @out.should == "E" + end +end + +describe DottedFormatter, "#finish" do + before :each do + @tally = double("tally").as_null_object + TallyAction.stub(:new).and_return(@tally) + @timer = double("timer").as_null_object + TimerAction.stub(:new).and_return(@timer) + + $stdout = @out = IOStub.new + context = ContextState.new "Class#method" + @state = ExampleState.new(context, "runs") + MSpec.stub(:register) + @formatter = DottedFormatter.new + @formatter.register + end + + after :each do + $stdout = STDOUT + end + + it "prints a failure message for an exception" do + exc = ExceptionState.new @state, nil, MSpecExampleError.new("broken") + @formatter.exception exc + @formatter.after @state + @formatter.finish + @out.should =~ /^1\)\nClass#method runs ERROR$/ + end + + it "prints a backtrace for an exception" do + exc = ExceptionState.new @state, nil, MSpecExampleError.new("broken") + exc.stub(:backtrace).and_return("path/to/some/file.rb:35:in method") + @formatter.exception exc + @formatter.after @state + @formatter.finish + @out.should =~ %r[path/to/some/file.rb:35:in method$] + end + + it "prints a summary of elapsed time" do + @timer.should_receive(:format).and_return("Finished in 2.0 seconds") + @formatter.finish + @out.should =~ /^Finished in 2.0 seconds$/ + end + + it "prints a tally of counts" do + @tally.should_receive(:format).and_return("1 example, 0 failures") + @formatter.finish + @out.should =~ /^1 example, 0 failures$/ + end + + it "prints errors, backtraces, elapsed time, and tallies" do + exc = ExceptionState.new @state, nil, MSpecExampleError.new("broken") + exc.stub(:backtrace).and_return("path/to/some/file.rb:35:in method") + @formatter.exception exc + @timer.should_receive(:format).and_return("Finished in 2.0 seconds") + @tally.should_receive(:format).and_return("1 example, 1 failure") + @formatter.after @state + @formatter.finish + @out.should == +%[E + +1) +Class#method runs ERROR +MSpecExampleError: broken +path/to/some/file.rb:35:in method + +Finished in 2.0 seconds + +1 example, 1 failure +] + end +end diff --git a/spec/mspec/spec/runner/formatters/file_spec.rb b/spec/mspec/spec/runner/formatters/file_spec.rb new file mode 100644 index 0000000000..946683ad58 --- /dev/null +++ b/spec/mspec/spec/runner/formatters/file_spec.rb @@ -0,0 +1,84 @@ +require File.dirname(__FILE__) + '/../../spec_helper' +require 'mspec/runner/formatters/file' +require 'mspec/runner/mspec' +require 'mspec/runner/example' + +describe FileFormatter, "#register" do + before :each do + @formatter = FileFormatter.new + MSpec.stub(:register) + MSpec.stub(:unregister) + end + + it "registers self with MSpec for :load, :unload actions" do + MSpec.should_receive(:register).with(:load, @formatter) + MSpec.should_receive(:register).with(:unload, @formatter) + @formatter.register + end + + it "unregisters self with MSpec for :before, :after actions" do + MSpec.should_receive(:unregister).with(:before, @formatter) + MSpec.should_receive(:unregister).with(:after, @formatter) + @formatter.register + end +end + +describe FileFormatter, "#load" do + before :each do + @state = ExampleState.new ContextState.new("describe"), "it" + @formatter = FileFormatter.new + @formatter.exception ExceptionState.new(nil, nil, SpecExpectationNotMetError.new("Failed!")) + end + + it "resets the #failure? flag to false" do + @formatter.failure?.should be_true + @formatter.load @state + @formatter.failure?.should be_false + end + + it "resets the #exception? flag to false" do + @formatter.exception?.should be_true + @formatter.load @state + @formatter.exception?.should be_false + end +end + +describe FileFormatter, "#unload" do + before :each do + $stdout = @out = IOStub.new + @formatter = FileFormatter.new + @state = ExampleState.new ContextState.new("describe"), "it" + end + + after :each do + $stdout = STDOUT + end + + it "prints a '.' if there was no exception raised" do + @formatter.unload(@state) + @out.should == "." + end + + it "prints an 'F' if there was an expectation failure" do + exc = SpecExpectationNotMetError.new "failed" + @formatter.exception ExceptionState.new(@state, nil, exc) + @formatter.unload(@state) + @out.should == "F" + end + + it "prints an 'E' if there was an exception other than expectation failure" do + exc = MSpecExampleError.new("boom!") + @formatter.exception ExceptionState.new(@state, nil, exc) + @formatter.unload(@state) + @out.should == "E" + end + + it "prints an 'E' if there are mixed exceptions and exepctation failures" do + exc = SpecExpectationNotMetError.new "failed" + @formatter.exception ExceptionState.new(@state, nil, exc) + exc = MSpecExampleError.new("boom!") + @formatter.exception ExceptionState.new(@state, nil, exc) + @formatter.unload(@state) + @out.should == "E" + end +end diff --git a/spec/mspec/spec/runner/formatters/html_spec.rb b/spec/mspec/spec/runner/formatters/html_spec.rb new file mode 100644 index 0000000000..d2aff1b2a7 --- /dev/null +++ b/spec/mspec/spec/runner/formatters/html_spec.rb @@ -0,0 +1,217 @@ +require File.dirname(__FILE__) + '/../../spec_helper' +require 'mspec/utils/ruby_name' +require 'mspec/guards/guard' +require 'mspec/runner/formatters/html' +require 'mspec/runner/mspec' +require 'mspec/runner/example' +require 'mspec/utils/script' + +describe HtmlFormatter do + before :each do + @formatter = HtmlFormatter.new + end + + it "responds to #register by registering itself with MSpec for appropriate actions" do + MSpec.stub(:register) + MSpec.should_receive(:register).with(:start, @formatter) + MSpec.should_receive(:register).with(:enter, @formatter) + MSpec.should_receive(:register).with(:leave, @formatter) + @formatter.register + end +end + +describe HtmlFormatter, "#start" do + before :each do + $stdout = @out = IOStub.new + @formatter = HtmlFormatter.new + end + + after :each do + $stdout = STDOUT + end + + it "prints the HTML head" do + @formatter.start + ruby_name = RUBY_NAME + ruby_name.should =~ /^#{ruby_name}/ + @out.should == +%[ + + +Spec Output For #{ruby_name} (#{RUBY_VERSION}) + + + +] + end +end + +describe HtmlFormatter, "#enter" do + before :each do + $stdout = @out = IOStub.new + @formatter = HtmlFormatter.new + end + + after :each do + $stdout = STDOUT + end + + it "prints the #describe string" do + @formatter.enter "describe" + @out.should == "

    describe

    \n
      \n" + end +end + +describe HtmlFormatter, "#leave" do + before :each do + $stdout = @out = IOStub.new + @formatter = HtmlFormatter.new + end + + after :each do + $stdout = STDOUT + end + + it "prints the closing tags for the #describe string" do + @formatter.leave + @out.should == "
    \n
    \n" + end +end + +describe HtmlFormatter, "#exception" do + before :each do + $stdout = @out = IOStub.new + @formatter = HtmlFormatter.new + @formatter.register + @state = ExampleState.new ContextState.new("describe"), "it" + end + + after :each do + $stdout = STDOUT + end + + it "prints the #it string once for each exception raised" do + exc = ExceptionState.new @state, nil, SpecExpectationNotMetError.new("disappointing") + @formatter.exception exc + exc = ExceptionState.new @state, nil, MSpecExampleError.new("painful") + @formatter.exception exc + @out.should == +%[
  • - it (FAILED - 1)
  • +
  • - it (ERROR - 2)
  • +] + end +end + +describe HtmlFormatter, "#after" do + before :each do + $stdout = @out = IOStub.new + @formatter = HtmlFormatter.new + @formatter.register + @state = ExampleState.new ContextState.new("describe"), "it" + end + + after :each do + $stdout = STDOUT + end + + it "prints the #it once when there are no exceptions raised" do + @formatter.after @state + @out.should == %[
  • - it
  • \n] + end + + it "does not print any output if an exception is raised" do + exc = ExceptionState.new @state, nil, SpecExpectationNotMetError.new("disappointing") + @formatter.exception exc + out = @out.dup + @formatter.after @state + @out.should == out + end +end + +describe HtmlFormatter, "#finish" do + before :each do + @tally = double("tally").as_null_object + TallyAction.stub(:new).and_return(@tally) + @timer = double("timer").as_null_object + TimerAction.stub(:new).and_return(@timer) + + $stdout = @out = IOStub.new + context = ContextState.new "describe" + @state = ExampleState.new(context, "it") + MSpec.stub(:register) + @formatter = HtmlFormatter.new + @formatter.register + @exception = MSpecExampleError.new("broken") + @exception.stub(:backtrace).and_return(["file.rb:1", "file.rb:2"]) + end + + after :each do + $stdout = STDOUT + end + + it "prints a failure message for an exception" do + exc = ExceptionState.new @state, nil, @exception + @formatter.exception exc + @formatter.finish + @out.should include "

    describe it ERROR

    " + end + + it "prints a backtrace for an exception" do + exc = ExceptionState.new @state, nil, @exception + exc.stub(:backtrace).and_return("path/to/some/file.rb:35:in method") + @formatter.exception exc + @formatter.finish + @out.should =~ %r[
    .*path/to/some/file.rb:35:in method.*
    ]m + end + + it "prints a summary of elapsed time" do + @timer.should_receive(:format).and_return("Finished in 2.0 seconds") + @formatter.finish + @out.should include "

    Finished in 2.0 seconds

    \n" + end + + it "prints a tally of counts" do + @tally.should_receive(:format).and_return("1 example, 0 failures") + @formatter.finish + @out.should include '

    1 example, 0 failures

    ' + end + + it "prints errors, backtraces, elapsed time, and tallies" do + exc = ExceptionState.new @state, nil, @exception + exc.stub(:backtrace).and_return("path/to/some/file.rb:35:in method") + @formatter.exception exc + + @timer.should_receive(:format).and_return("Finished in 2.0 seconds") + @tally.should_receive(:format).and_return("1 example, 1 failures") + @formatter.finish + @out.should == +%[
  • - it (ERROR - 1)
  • +
    +
      +
    1. describe it ERROR

      +

      MSpecExampleError: broken

      +
      +path/to/some/file.rb:35:in method
      +
    2. +
    +

    Finished in 2.0 seconds

    +

    1 example, 1 failures

    + + +] + end +end diff --git a/spec/mspec/spec/runner/formatters/junit_spec.rb b/spec/mspec/spec/runner/formatters/junit_spec.rb new file mode 100644 index 0000000000..66e7d70e92 --- /dev/null +++ b/spec/mspec/spec/runner/formatters/junit_spec.rb @@ -0,0 +1,147 @@ +# -*- coding: utf-8 -*- +require File.dirname(__FILE__) + '/../../spec_helper' +require 'mspec/runner/formatters/junit' +require 'mspec/runner/example' + +describe JUnitFormatter, "#initialize" do + it "permits zero arguments" do + lambda { JUnitFormatter.new }.should_not raise_error + end + + it "accepts one argument" do + lambda { JUnitFormatter.new nil }.should_not raise_error + end +end + +describe JUnitFormatter, "#print" do + before :each do + $stdout = IOStub.new + @out = IOStub.new + File.stub(:open).and_return(@out) + @formatter = JUnitFormatter.new "some/file" + end + + after :each do + $stdout = STDOUT + end + + it "writes to $stdout if #switch has not been called" do + @formatter.print "begonias" + $stdout.should == "begonias" + @out.should == "" + end + + it "writes to the file passed to #initialize once #switch has been called" do + @formatter.switch + @formatter.print "begonias" + $stdout.should == "" + @out.should == "begonias" + end + + it "writes to $stdout once #switch is called if no file was passed to #initialize" do + formatter = JUnitFormatter.new + formatter.switch + formatter.print "begonias" + $stdout.should == "begonias" + @out.should == "" + end +end + +describe JUnitFormatter, "#finish" do + before :each do + @tally = double("tally").as_null_object + @counter = double("counter").as_null_object + @tally.stub(:counter).and_return(@counter) + TallyAction.stub(:new).and_return(@tally) + + @timer = double("timer").as_null_object + TimerAction.stub(:new).and_return(@timer) + + $stdout = IOStub.new + context = ContextState.new "describe" + @state = ExampleState.new(context, "it") + + @formatter = JUnitFormatter.new + @formatter.stub(:backtrace).and_return("") + MSpec.stub(:register) + @formatter.register + + exc = ExceptionState.new @state, nil, MSpecExampleError.new("broken") + exc.stub(:backtrace).and_return("path/to/some/file.rb:35:in method") + @formatter.exception exc + @formatter.after @state + end + + after :each do + $stdout = STDOUT + end + + it "calls #switch" do + @formatter.should_receive(:switch) + @formatter.finish + end + + it "outputs a failure message and backtrace" do + @formatter.finish + $stdout.should include 'message="error in describe it" type="error"' + $stdout.should include "MSpecExampleError: broken\n" + $stdout.should include "path/to/some/file.rb:35:in method" + end + + it "encodes message and backtrace in latin1 for jenkins" do + exc = ExceptionState.new @state, nil, MSpecExampleError.new("broken…") + exc.stub(:backtrace).and_return("path/to/some/file.rb:35:in methød") + @formatter.exception exc + @formatter.finish + $stdout.should =~ /MSpecExampleError: broken((\.\.\.)|\?)\n/ + $stdout.should =~ /path\/to\/some\/file\.rb:35:in meth(\?|o)d/ + end + + it "outputs an elapsed time" do + @timer.should_receive(:elapsed).and_return(4.2) + @formatter.finish + $stdout.should include 'time="4.2"' + end + + it "outputs overall elapsed time" do + @timer.should_receive(:elapsed).and_return(4.2) + @formatter.finish + $stdout.should include 'timeCount="4.2"' + end + + it "outputs the number of examples as test count" do + @counter.should_receive(:examples).and_return(9) + @formatter.finish + $stdout.should include 'tests="9"' + end + + it "outputs overall number of examples as test count" do + @counter.should_receive(:examples).and_return(9) + @formatter.finish + $stdout.should include 'testCount="9"' + end + + it "outputs a failure count" do + @counter.should_receive(:failures).and_return(2) + @formatter.finish + $stdout.should include 'failureCount="2"' + end + + it "outputs overall failure count" do + @counter.should_receive(:failures).and_return(2) + @formatter.finish + $stdout.should include 'failures="2"' + end + + it "outputs an error count" do + @counter.should_receive(:errors).and_return(1) + @formatter.finish + $stdout.should include 'errors="1"' + end + + it "outputs overall error count" do + @counter.should_receive(:errors).and_return(1) + @formatter.finish + $stdout.should include 'errorCount="1"' + end +end diff --git a/spec/mspec/spec/runner/formatters/method_spec.rb b/spec/mspec/spec/runner/formatters/method_spec.rb new file mode 100644 index 0000000000..77204f74c5 --- /dev/null +++ b/spec/mspec/spec/runner/formatters/method_spec.rb @@ -0,0 +1,178 @@ +require File.dirname(__FILE__) + '/../../spec_helper' +require 'mspec/runner/formatters/method' +require 'mspec/runner/mspec' +require 'mspec/runner/example' +require 'mspec/utils/script' + +describe MethodFormatter, "#method_type" do + before :each do + @formatter = MethodFormatter.new + end + + it "returns 'class' if the separator is '.' or '::'" do + @formatter.method_type('.').should == "class" + @formatter.method_type('::').should == "class" + end + + it "returns 'instance' if the separator is '#'" do + @formatter.method_type('#').should == "instance" + end + + it "returns 'unknown' for all other cases" do + @formatter.method_type(nil).should == "unknown" + end +end + +describe MethodFormatter, "#before" do + before :each do + @formatter = MethodFormatter.new + MSpec.stub(:register) + @formatter.register + end + + it "resets the tally counters to 0" do + @formatter.tally.counter.examples = 3 + @formatter.tally.counter.expectations = 4 + @formatter.tally.counter.failures = 2 + @formatter.tally.counter.errors = 1 + + state = ExampleState.new ContextState.new("describe"), "it" + @formatter.before state + @formatter.tally.counter.examples.should == 0 + @formatter.tally.counter.expectations.should == 0 + @formatter.tally.counter.failures.should == 0 + @formatter.tally.counter.errors.should == 0 + end + + it "records the class, method if available" do + state = ExampleState.new ContextState.new("Some#method"), "it" + @formatter.before state + key = "Some#method" + @formatter.methods.keys.should include(key) + h = @formatter.methods[key] + h[:class].should == "Some" + h[:method].should == "method" + h[:description].should == "Some#method it" + end + + it "does not record class, method unless both are available" do + state = ExampleState.new ContextState.new("Some method"), "it" + @formatter.before state + key = "Some method" + @formatter.methods.keys.should include(key) + h = @formatter.methods[key] + h[:class].should == "" + h[:method].should == "" + h[:description].should == "Some method it" + end + + it "sets the method type to unknown if class and method are not available" do + state = ExampleState.new ContextState.new("Some method"), "it" + @formatter.before state + key = "Some method" + h = @formatter.methods[key] + h[:type].should == "unknown" + end + + it "sets the method type based on the class, method separator" do + [["C#m", "instance"], ["C.m", "class"], ["C::m", "class"]].each do |k, t| + state = ExampleState.new ContextState.new(k), "it" + @formatter.before state + h = @formatter.methods[k] + h[:type].should == t + end + end + + it "clears the list of exceptions" do + state = ExampleState.new ContextState.new("describe"), "it" + @formatter.exceptions << "stuff" + @formatter.before state + @formatter.exceptions.should be_empty + end +end + +describe MethodFormatter, "#after" do + before :each do + @formatter = MethodFormatter.new + MSpec.stub(:register) + @formatter.register + end + + it "sets the tally counts" do + state = ExampleState.new ContextState.new("Some#method"), "it" + @formatter.before state + + @formatter.tally.counter.examples = 3 + @formatter.tally.counter.expectations = 4 + @formatter.tally.counter.failures = 2 + @formatter.tally.counter.errors = 1 + + @formatter.after state + h = @formatter.methods["Some#method"] + h[:examples].should == 3 + h[:expectations].should == 4 + h[:failures].should == 2 + h[:errors].should == 1 + end + + it "renders the list of exceptions" do + state = ExampleState.new ContextState.new("Some#method"), "it" + @formatter.before state + + exc = SpecExpectationNotMetError.new "failed" + @formatter.exception ExceptionState.new(state, nil, exc) + @formatter.exception ExceptionState.new(state, nil, exc) + + @formatter.after state + h = @formatter.methods["Some#method"] + h[:exceptions].should == [ + %[failed\n\n], + %[failed\n\n] + ] + end +end + +describe MethodFormatter, "#after" do + before :each do + $stdout = IOStub.new + context = ContextState.new "Class#method" + @state = ExampleState.new(context, "runs") + @formatter = MethodFormatter.new + MSpec.stub(:register) + @formatter.register + end + + after :each do + $stdout = STDOUT + end + + it "prints a summary of the results of an example in YAML format" do + @formatter.before @state + @formatter.tally.counter.examples = 3 + @formatter.tally.counter.expectations = 4 + @formatter.tally.counter.failures = 2 + @formatter.tally.counter.errors = 1 + + exc = SpecExpectationNotMetError.new "failed" + @formatter.exception ExceptionState.new(@state, nil, exc) + @formatter.exception ExceptionState.new(@state, nil, exc) + + @formatter.after @state + @formatter.finish + $stdout.should == +%[--- +"Class#method": + class: "Class" + method: "method" + type: instance + description: "Class#method runs" + examples: 3 + expectations: 4 + failures: 2 + errors: 1 + exceptions: + - "failed\\n\\n" + - "failed\\n\\n" +] + end +end diff --git a/spec/mspec/spec/runner/formatters/multi_spec.rb b/spec/mspec/spec/runner/formatters/multi_spec.rb new file mode 100644 index 0000000000..afcc7e9cea --- /dev/null +++ b/spec/mspec/spec/runner/formatters/multi_spec.rb @@ -0,0 +1,68 @@ +require File.dirname(__FILE__) + '/../../spec_helper' +require 'mspec/runner/formatters/multi' +require 'mspec/runner/example' + +describe MultiFormatter, "#aggregate_results" do + before :each do + @stdout, $stdout = $stdout, IOStub.new + + @file = double("file").as_null_object + + File.stub(:delete) + YAML.stub(:load) + + @hash = { "files"=>1, "examples"=>1, "expectations"=>2, "failures"=>0, "errors"=>0 } + File.stub(:open).and_yield(@file).and_return(@hash) + + @formatter = MultiFormatter.new + @formatter.timer.stub(:format).and_return("Finished in 42 seconds") + end + + after :each do + $stdout = @stdout + end + + it "outputs a summary without errors" do + @formatter.aggregate_results(["a", "b"]) + @formatter.finish + $stdout.should == +%[ + +Finished in 42 seconds + +2 files, 2 examples, 4 expectations, 0 failures, 0 errors, 0 tagged +] + end + + it "outputs a summary with errors" do + @hash["exceptions"] = [ + "Some#method works real good FAILED\nExpected real good\n to equal fail\n\nfoo.rb:1\nfoo.rb:2", + "Some#method never fails ERROR\nExpected 5\n to equal 3\n\nfoo.rb:1\nfoo.rb:2" + ] + @formatter.aggregate_results(["a"]) + @formatter.finish + $stdout.should == +%[ + +1) +Some#method works real good FAILED +Expected real good + to equal fail + +foo.rb:1 +foo.rb:2 + +2) +Some#method never fails ERROR +Expected 5 + to equal 3 + +foo.rb:1 +foo.rb:2 + +Finished in 42 seconds + +1 file, 1 example, 2 expectations, 0 failures, 0 errors, 0 tagged +] + end +end diff --git a/spec/mspec/spec/runner/formatters/specdoc_spec.rb b/spec/mspec/spec/runner/formatters/specdoc_spec.rb new file mode 100644 index 0000000000..edb439fc11 --- /dev/null +++ b/spec/mspec/spec/runner/formatters/specdoc_spec.rb @@ -0,0 +1,106 @@ +require File.dirname(__FILE__) + '/../../spec_helper' +require 'mspec/runner/formatters/specdoc' +require 'mspec/runner/example' + +describe SpecdocFormatter do + before :each do + @formatter = SpecdocFormatter.new + end + + it "responds to #register by registering itself with MSpec for appropriate actions" do + MSpec.stub(:register) + MSpec.should_receive(:register).with(:enter, @formatter) + @formatter.register + end +end + +describe SpecdocFormatter, "#enter" do + before :each do + $stdout = @out = IOStub.new + @formatter = SpecdocFormatter.new + end + + after :each do + $stdout = STDOUT + end + + it "prints the #describe string" do + @formatter.enter("describe") + @out.should == "\ndescribe\n" + end +end + +describe SpecdocFormatter, "#before" do + before :each do + $stdout = @out = IOStub.new + @formatter = SpecdocFormatter.new + @state = ExampleState.new ContextState.new("describe"), "it" + end + + after :each do + $stdout = STDOUT + end + + it "prints the #it string" do + @formatter.before @state + @out.should == "- it" + end + + it "resets the #exception? flag" do + exc = ExceptionState.new @state, nil, SpecExpectationNotMetError.new("disappointing") + @formatter.exception exc + @formatter.exception?.should be_true + @formatter.before @state + @formatter.exception?.should be_false + end +end + +describe SpecdocFormatter, "#exception" do + before :each do + $stdout = @out = IOStub.new + @formatter = SpecdocFormatter.new + context = ContextState.new "describe" + @state = ExampleState.new context, "it" + end + + after :each do + $stdout = STDOUT + end + + it "prints 'ERROR' if an exception is not an SpecExpectationNotMetError" do + exc = ExceptionState.new @state, nil, MSpecExampleError.new("painful") + @formatter.exception exc + @out.should == " (ERROR - 1)" + end + + it "prints 'FAILED' if an exception is an SpecExpectationNotMetError" do + exc = ExceptionState.new @state, nil, SpecExpectationNotMetError.new("disappointing") + @formatter.exception exc + @out.should == " (FAILED - 1)" + end + + it "prints the #it string if an exception has already been raised" do + exc = ExceptionState.new @state, nil, SpecExpectationNotMetError.new("disappointing") + @formatter.exception exc + exc = ExceptionState.new @state, nil, MSpecExampleError.new("painful") + @formatter.exception exc + @out.should == " (FAILED - 1)\n- it (ERROR - 2)" + end +end + +describe SpecdocFormatter, "#after" do + before :each do + $stdout = @out = IOStub.new + @formatter = SpecdocFormatter.new + @state = ExampleState.new "describe", "it" + end + + after :each do + $stdout = STDOUT + end + + it "prints a newline character" do + @formatter.after @state + @out.should == "\n" + end +end diff --git a/spec/mspec/spec/runner/formatters/spinner_spec.rb b/spec/mspec/spec/runner/formatters/spinner_spec.rb new file mode 100644 index 0000000000..a122620e39 --- /dev/null +++ b/spec/mspec/spec/runner/formatters/spinner_spec.rb @@ -0,0 +1,83 @@ +require File.dirname(__FILE__) + '/../../spec_helper' +require 'mspec/runner/formatters/spinner' +require 'mspec/runner/mspec' +require 'mspec/runner/example' + +describe SpinnerFormatter, "#initialize" do + it "permits zero arguments" do + SpinnerFormatter.new + end + + it "accepts one argument" do + SpinnerFormatter.new nil + end +end + +describe SpinnerFormatter, "#register" do + before :each do + @formatter = SpinnerFormatter.new + MSpec.stub(:register) + end + + it "registers self with MSpec for appropriate actions" do + MSpec.should_receive(:register).with(:start, @formatter) + MSpec.should_receive(:register).with(:unload, @formatter) + MSpec.should_receive(:register).with(:after, @formatter) + MSpec.should_receive(:register).with(:finish, @formatter) + @formatter.register + end + + it "creates TimerAction and TallyAction" do + timer = double("timer") + tally = double("tally") + timer.should_receive(:register) + tally.should_receive(:register) + tally.should_receive(:counter) + TimerAction.should_receive(:new).and_return(timer) + TallyAction.should_receive(:new).and_return(tally) + @formatter.register + end +end + +describe SpinnerFormatter, "#print" do + after :each do + $stdout = STDOUT + end + + it "ignores the argument to #initialize and writes to $stdout" do + $stdout = IOStub.new + formatter = SpinnerFormatter.new "some/file" + formatter.print "begonias" + $stdout.should == "begonias" + end +end + +describe SpinnerFormatter, "#after" do + before :each do + $stdout = IOStub.new + MSpec.store(:files, ["a", "b", "c", "d"]) + @formatter = SpinnerFormatter.new + @formatter.register + @state = ExampleState.new("describe", "it") + end + + after :each do + $stdout = STDOUT + end + + it "updates the spinner" do + @formatter.start + @formatter.after @state + @formatter.unload + + if ENV["TERM"] != "dumb" + green = "\e[0;32m" + reset = "\e[0m" + end + + output = "\r[/ | 0% | 00:00:00] #{green} 0F #{green} 0E#{reset} " \ + "\r[- | 0% | 00:00:00] #{green} 0F #{green} 0E#{reset} " \ + "\r[\\ | ========== 25% | 00:00:00] #{green} 0F #{green} 0E#{reset} " + $stdout.should == output + end +end diff --git a/spec/mspec/spec/runner/formatters/summary_spec.rb b/spec/mspec/spec/runner/formatters/summary_spec.rb new file mode 100644 index 0000000000..16a156b695 --- /dev/null +++ b/spec/mspec/spec/runner/formatters/summary_spec.rb @@ -0,0 +1,26 @@ +require File.dirname(__FILE__) + '/../../spec_helper' +require 'mspec/runner/formatters/summary' +require 'mspec/runner/example' + +describe SummaryFormatter, "#after" do + before :each do + $stdout = @out = IOStub.new + @formatter = SummaryFormatter.new + @formatter.register + context = ContextState.new "describe" + @state = ExampleState.new(context, "it") + end + + after :each do + $stdout = STDOUT + end + + it "does not print anything" do + exc = ExceptionState.new @state, nil, SpecExpectationNotMetError.new("disappointing") + @formatter.exception exc + exc = ExceptionState.new @state, nil, MSpecExampleError.new("painful") + @formatter.exception exc + @formatter.after(@state) + @out.should == "" + end +end diff --git a/spec/mspec/spec/runner/formatters/unit_spec.rb b/spec/mspec/spec/runner/formatters/unit_spec.rb new file mode 100644 index 0000000000..c8ba406f51 --- /dev/null +++ b/spec/mspec/spec/runner/formatters/unit_spec.rb @@ -0,0 +1,74 @@ +require File.dirname(__FILE__) + '/../../spec_helper' +require 'mspec/runner/formatters/unit' +require 'mspec/runner/example' +require 'mspec/utils/script' + +describe UnitdiffFormatter, "#finish" do + before :each do + @tally = double("tally").as_null_object + TallyAction.stub(:new).and_return(@tally) + @timer = double("timer").as_null_object + TimerAction.stub(:new).and_return(@timer) + + $stdout = @out = IOStub.new + context = ContextState.new "describe" + @state = ExampleState.new(context, "it") + MSpec.stub(:register) + @formatter = UnitdiffFormatter.new + @formatter.register + end + + after :each do + $stdout = STDOUT + end + + it "prints a failure message for an exception" do + exc = ExceptionState.new @state, nil, MSpecExampleError.new("broken") + @formatter.exception exc + @formatter.after @state + @formatter.finish + @out.should =~ /^1\)\ndescribe it ERROR$/ + end + + it "prints a backtrace for an exception" do + exc = ExceptionState.new @state, nil, Exception.new("broken") + exc.stub(:backtrace).and_return("path/to/some/file.rb:35:in method") + @formatter.exception exc + @formatter.finish + @out.should =~ %r[path/to/some/file.rb:35:in method$] + end + + it "prints a summary of elapsed time" do + @timer.should_receive(:format).and_return("Finished in 2.0 seconds") + @formatter.finish + @out.should =~ /^Finished in 2.0 seconds$/ + end + + it "prints a tally of counts" do + @tally.should_receive(:format).and_return("1 example, 0 failures") + @formatter.finish + @out.should =~ /^1 example, 0 failures$/ + end + + it "prints errors, backtraces, elapsed time, and tallies" do + exc = ExceptionState.new @state, nil, Exception.new("broken") + exc.stub(:backtrace).and_return("path/to/some/file.rb:35:in method") + @formatter.exception exc + @formatter.after @state + @timer.should_receive(:format).and_return("Finished in 2.0 seconds") + @tally.should_receive(:format).and_return("1 example, 0 failures") + @formatter.finish + @out.should == +%[E + +Finished in 2.0 seconds + +1) +describe it ERROR +Exception: broken: +path/to/some/file.rb:35:in method + +1 example, 0 failures +] + end +end diff --git a/spec/mspec/spec/runner/formatters/yaml_spec.rb b/spec/mspec/spec/runner/formatters/yaml_spec.rb new file mode 100644 index 0000000000..eb4d99f74c --- /dev/null +++ b/spec/mspec/spec/runner/formatters/yaml_spec.rb @@ -0,0 +1,125 @@ +require File.dirname(__FILE__) + '/../../spec_helper' +require 'mspec/runner/formatters/yaml' +require 'mspec/runner/example' + +describe YamlFormatter, "#initialize" do + it "permits zero arguments" do + YamlFormatter.new + end + + it "accepts one argument" do + YamlFormatter.new nil + end +end + +describe YamlFormatter, "#print" do + before :each do + $stdout = IOStub.new + @out = IOStub.new + File.stub(:open).and_return(@out) + @formatter = YamlFormatter.new "some/file" + end + + after :each do + $stdout = STDOUT + end + + it "writes to $stdout if #switch has not been called" do + @formatter.print "begonias" + $stdout.should == "begonias" + @out.should == "" + end + + it "writes to the file passed to #initialize once #switch has been called" do + @formatter.switch + @formatter.print "begonias" + $stdout.should == "" + @out.should == "begonias" + end + + it "writes to $stdout once #switch is called if no file was passed to #initialize" do + formatter = YamlFormatter.new + formatter.switch + formatter.print "begonias" + $stdout.should == "begonias" + @out.should == "" + end +end + +describe YamlFormatter, "#finish" do + before :each do + @tally = double("tally").as_null_object + @counter = double("counter").as_null_object + @tally.stub(:counter).and_return(@counter) + TallyAction.stub(:new).and_return(@tally) + + @timer = double("timer").as_null_object + TimerAction.stub(:new).and_return(@timer) + + $stdout = IOStub.new + context = ContextState.new "describe" + @state = ExampleState.new(context, "it") + + @formatter = YamlFormatter.new + @formatter.stub(:backtrace).and_return("") + MSpec.stub(:register) + @formatter.register + + exc = ExceptionState.new @state, nil, MSpecExampleError.new("broken") + exc.stub(:backtrace).and_return("path/to/some/file.rb:35:in method") + @formatter.exception exc + @formatter.after @state + end + + after :each do + $stdout = STDOUT + end + + it "calls #switch" do + @formatter.should_receive(:switch) + @formatter.finish + end + + it "outputs a failure message and backtrace" do + @formatter.finish + $stdout.should include "describe it ERROR" + $stdout.should include "MSpecExampleError: broken\\n" + $stdout.should include "path/to/some/file.rb:35:in method" + end + + it "outputs an elapsed time" do + @timer.should_receive(:elapsed).and_return(4.2) + @formatter.finish + $stdout.should include "time: 4.2" + end + + it "outputs a file count" do + @counter.should_receive(:files).and_return(3) + @formatter.finish + $stdout.should include "files: 3" + end + + it "outputs an example count" do + @counter.should_receive(:examples).and_return(3) + @formatter.finish + $stdout.should include "examples: 3" + end + + it "outputs an expectation count" do + @counter.should_receive(:expectations).and_return(9) + @formatter.finish + $stdout.should include "expectations: 9" + end + + it "outputs a failure count" do + @counter.should_receive(:failures).and_return(2) + @formatter.finish + $stdout.should include "failures: 2" + end + + it "outputs an error count" do + @counter.should_receive(:errors).and_return(1) + @formatter.finish + $stdout.should include "errors: 1" + end +end diff --git a/spec/mspec/spec/runner/mspec_spec.rb b/spec/mspec/spec/runner/mspec_spec.rb new file mode 100644 index 0000000000..9b8142414e --- /dev/null +++ b/spec/mspec/spec/runner/mspec_spec.rb @@ -0,0 +1,595 @@ +require 'spec_helper' +require 'mspec/helpers/tmp' +require 'mspec/helpers/fs' +require 'mspec/matchers/base' +require 'mspec/runner/mspec' +require 'mspec/runner/example' + +describe MSpec, ".register_files" do + it "records which spec files to run" do + MSpec.register_files [:one, :two, :three] + MSpec.retrieve(:files).should == [:one, :two, :three] + end +end + +describe MSpec, ".register_mode" do + before :each do + MSpec.clear_modes + end + + it "sets execution mode flags" do + MSpec.register_mode :verify + MSpec.retrieve(:modes).should == [:verify] + end +end + +describe MSpec, ".register_tags_patterns" do + it "records the patterns for generating a tag file from a spec file" do + MSpec.register_tags_patterns [[/spec\/ruby/, "spec/tags"], [/frozen/, "ruby"]] + MSpec.retrieve(:tags_patterns).should == [[/spec\/ruby/, "spec/tags"], [/frozen/, "ruby"]] + end +end + +describe MSpec, ".register_exit" do + before :each do + MSpec.store :exit, 0 + end + + it "records the exit code" do + MSpec.exit_code.should == 0 + MSpec.register_exit 1 + MSpec.exit_code.should == 1 + end +end + +describe MSpec, ".exit_code" do + it "retrieves the code set with .register_exit" do + MSpec.register_exit 99 + MSpec.exit_code.should == 99 + end +end + +describe MSpec, ".store" do + it "records data for MSpec settings" do + MSpec.store :anything, :value + MSpec.retrieve(:anything).should == :value + end +end + +describe MSpec, ".retrieve" do + it "accesses .store'd data" do + MSpec.register :action, :first + MSpec.retrieve(:action).should == [:first] + end +end + +describe MSpec, ".randomize" do + it "sets the flag to randomize spec execution order" do + MSpec.randomize?.should == false + MSpec.randomize + MSpec.randomize?.should == true + MSpec.randomize false + MSpec.randomize?.should == false + end +end + +describe MSpec, ".register" do + it "is the gateway behind the register(symbol, action) facility" do + MSpec.register :bonus, :first + MSpec.register :bonus, :second + MSpec.register :bonus, :second + MSpec.retrieve(:bonus).should == [:first, :second] + end +end + +describe MSpec, ".unregister" do + it "is the gateway behind the unregister(symbol, actions) facility" do + MSpec.register :unregister, :first + MSpec.register :unregister, :second + MSpec.unregister :unregister, :second + MSpec.retrieve(:unregister).should == [:first] + end +end + +describe MSpec, ".protect" do + before :each do + MSpec.clear_current + @cs = ContextState.new "C#m" + @cs.parent = MSpec.current + + @es = ExampleState.new @cs, "runs" + ScratchPad.record Exception.new("Sharp!") + end + + it "returns true if no exception is raised" do + MSpec.protect("passed") { 1 }.should be_true + end + + it "returns false if an exception is raised" do + MSpec.protect("testing") { raise ScratchPad.recorded }.should be_false + end + + it "rescues any exceptions raised when evaluating the block argument" do + MSpec.protect("") { raise Exception, "Now you see me..." } + end + + it "does not rescue SystemExit" do + begin + MSpec.protect("") { exit 1 } + rescue SystemExit + ScratchPad.record :system_exit + end + ScratchPad.recorded.should == :system_exit + end + + it "calls all the exception actions" do + exc = ExceptionState.new @es, "testing", ScratchPad.recorded + ExceptionState.stub(:new).and_return(exc) + action = double("exception") + action.should_receive(:exception).with(exc) + MSpec.register :exception, action + MSpec.protect("testing") { raise ScratchPad.recorded } + MSpec.unregister :exception, action + end + + it "registers a non-zero exit code when an exception is raised" do + MSpec.should_receive(:register_exit).with(1) + MSpec.protect("testing") { raise ScratchPad.recorded } + end +end + +describe MSpec, ".register_current" do + before :each do + MSpec.clear_current + end + + it "sets the value returned by MSpec.current" do + MSpec.current.should be_nil + MSpec.register_current :a + MSpec.current.should == :a + end +end + +describe MSpec, ".clear_current" do + it "sets the value returned by MSpec.current to nil" do + MSpec.register_current :a + MSpec.current.should_not be_nil + MSpec.clear_current + MSpec.current.should be_nil + end +end + +describe MSpec, ".current" do + before :each do + MSpec.clear_current + end + + it "returns nil if no ContextState has been registered" do + MSpec.current.should be_nil + end + + it "returns the most recently registered ContextState" do + first = ContextState.new "" + second = ContextState.new "" + MSpec.register_current first + MSpec.current.should == first + MSpec.register_current second + MSpec.current.should == second + end +end + +describe MSpec, ".actions" do + before :each do + MSpec.store :start, [] + ScratchPad.record [] + start_one = double("one") + start_one.stub(:start).and_return { ScratchPad << :one } + start_two = double("two") + start_two.stub(:start).and_return { ScratchPad << :two } + MSpec.register :start, start_one + MSpec.register :start, start_two + end + + it "does not attempt to run any actions if none have been registered" do + MSpec.store :finish, nil + lambda { MSpec.actions :finish }.should_not raise_error + end + + it "runs each action registered as a start action" do + MSpec.actions :start + ScratchPad.recorded.should == [:one, :two] + end +end + +describe MSpec, ".mode?" do + before :each do + MSpec.clear_modes + end + + it "returns true if the mode has been set" do + MSpec.mode?(:verify).should == false + MSpec.register_mode :verify + MSpec.mode?(:verify).should == true + end +end + +describe MSpec, ".clear_modes" do + it "clears all registered modes" do + MSpec.register_mode(:pretend) + MSpec.register_mode(:verify) + + MSpec.mode?(:pretend).should == true + MSpec.mode?(:verify).should == true + + MSpec.clear_modes + + MSpec.mode?(:pretend).should == false + MSpec.mode?(:verify).should == false + end +end + +describe MSpec, ".guarded?" do + before :each do + MSpec.instance_variable_set :@guarded, [] + end + + it "returns false if no guard has run" do + MSpec.guarded?.should == false + end + + it "returns true if a single guard has run" do + MSpec.guard + MSpec.guarded?.should == true + end + + it "returns true if more than one guard has run" do + MSpec.guard + MSpec.guard + MSpec.guarded?.should == true + end + + it "returns true until all guards have finished" do + MSpec.guard + MSpec.guard + MSpec.guarded?.should == true + MSpec.unguard + MSpec.guarded?.should == true + MSpec.unguard + MSpec.guarded?.should == false + end +end + +describe MSpec, ".describe" do + before :each do + MSpec.clear_current + @cs = ContextState.new "" + ContextState.stub(:new).and_return(@cs) + MSpec.stub(:current).and_return(nil) + MSpec.stub(:register_current) + end + + it "creates a new ContextState for the block" do + ContextState.should_receive(:new).and_return(@cs) + MSpec.describe(Object) { } + end + + it "accepts an optional second argument" do + ContextState.should_receive(:new).and_return(@cs) + MSpec.describe(Object, "msg") { } + end + + it "registers the newly created ContextState" do + MSpec.should_receive(:register_current).with(@cs).twice + MSpec.describe(Object) { } + end + + it "invokes the ContextState#describe method" do + prc = lambda { } + @cs.should_receive(:describe).with(&prc) + MSpec.describe(Object, "msg", &prc) + end +end + +describe MSpec, ".process" do + before :each do + MSpec.stub(:files) + MSpec.store :start, [] + MSpec.store :finish, [] + STDOUT.stub(:puts) + end + + it "prints the RUBY_DESCRIPTION" do + STDOUT.should_receive(:puts).with(RUBY_DESCRIPTION) + MSpec.process + end + + it "calls all start actions" do + start = double("start") + start.stub(:start).and_return { ScratchPad.record :start } + MSpec.register :start, start + MSpec.process + ScratchPad.recorded.should == :start + end + + it "calls all finish actions" do + finish = double("finish") + finish.stub(:finish).and_return { ScratchPad.record :finish } + MSpec.register :finish, finish + MSpec.process + ScratchPad.recorded.should == :finish + end + + it "calls the files method" do + MSpec.should_receive(:files) + MSpec.process + end +end + +describe MSpec, ".files" do + before :each do + MSpec.store :load, [] + MSpec.store :unload, [] + MSpec.register_files [:one, :two, :three] + Kernel.stub(:load) + end + + it "calls load actions before each file" do + load = double("load") + load.stub(:load).and_return { ScratchPad.record :load } + MSpec.register :load, load + MSpec.files + ScratchPad.recorded.should == :load + end + + it "shuffles the file list if .randomize? is true" do + MSpec.randomize + MSpec.should_receive(:shuffle) + MSpec.files + MSpec.randomize false + end + + it "registers the current file" do + MSpec.should_receive(:store).with(:file, :one) + MSpec.should_receive(:store).with(:file, :two) + MSpec.should_receive(:store).with(:file, :three) + MSpec.files + end +end + +describe MSpec, ".shuffle" do + before :each do + @base = (0..100).to_a + @list = @base.clone + MSpec.shuffle @list + end + + it "does not alter the elements in the list" do + @base.each do |elt| + @list.should include(elt) + end + end + + it "changes the order of the list" do + # obviously, this spec has a certain probability + # of failing. If it fails, run it again. + @list.should_not == @base + end +end + +describe MSpec, ".tags_file" do + before :each do + MSpec.store :file, "path/to/spec/something/some_spec.rb" + MSpec.store :tags_patterns, nil + end + + it "returns the default tags file for the current spec file" do + MSpec.tags_file.should == "path/to/spec/tags/something/some_tags.txt" + end + + it "returns the tags file for the current spec file with custom tags_patterns" do + MSpec.register_tags_patterns [[/^(.*)\/spec/, '\1/tags'], [/_spec.rb/, "_tags.txt"]] + MSpec.tags_file.should == "path/to/tags/something/some_tags.txt" + end + + it "performs multiple substitutions" do + MSpec.register_tags_patterns [ + [%r(/spec/something/), "/spec/other/"], + [%r(/spec/), "/spec/tags/"], + [/_spec.rb/, "_tags.txt"] + ] + MSpec.tags_file.should == "path/to/spec/tags/other/some_tags.txt" + end + + it "handles cases where no substitution is performed" do + MSpec.register_tags_patterns [[/nothing/, "something"]] + MSpec.tags_file.should == "path/to/spec/something/some_spec.rb" + end +end + +describe MSpec, ".read_tags" do + before :each do + MSpec.stub(:tags_file).and_return(File.dirname(__FILE__) + '/tags.txt') + end + + it "returns a list of tag instances for matching tag names found" do + one = SpecTag.new "fail(broken):Some#method? works" + MSpec.read_tags(["fail", "pass"]).should == [one] + end + + it "returns [] if no tags names match" do + MSpec.read_tags("super").should == [] + end +end + +describe MSpec, ".read_tags" do + before :each do + @tag = SpecTag.new "fails:Some#method" + File.open(tmp("tags.txt", false), "w") do |f| + f.puts "" + f.puts @tag + f.puts "" + end + MSpec.stub(:tags_file).and_return(tmp("tags.txt", false)) + end + + it "does not return a tag object for empty lines" do + MSpec.read_tags(["fails"]).should == [@tag] + end +end + +describe MSpec, ".write_tags" do + before :each do + FileUtils.cp File.dirname(__FILE__) + "/tags.txt", tmp("tags.txt", false) + MSpec.stub(:tags_file).and_return(tmp("tags.txt", false)) + @tag1 = SpecTag.new "check(broken):Tag#rewrite works" + @tag2 = SpecTag.new "broken:Tag#write_tags fails" + end + + after :all do + rm_r tmp("tags.txt", false) + end + + it "overwrites the tags in the tag file" do + IO.read(tmp("tags.txt", false)).should == %[fail(broken):Some#method? works +incomplete(20%):The#best method ever +benchmark(0.01825):The#fastest method today +extended():\"Multi-line\\ntext\\ntag\" +] + MSpec.write_tags [@tag1, @tag2] + IO.read(tmp("tags.txt", false)).should == %[check(broken):Tag#rewrite works +broken:Tag#write_tags fails +] + end +end + +describe MSpec, ".write_tag" do + before :each do + FileUtils.stub(:mkdir_p) + MSpec.stub(:tags_file).and_return(tmp("tags.txt", false)) + @tag = SpecTag.new "fail(broken):Some#method works" + end + + after :all do + rm_r tmp("tags.txt", false) + end + + it "writes a tag to the tags file for the current spec file" do + MSpec.write_tag @tag + IO.read(tmp("tags.txt", false)).should == "fail(broken):Some#method works\n" + end + + it "does not write a duplicate tag" do + File.open(tmp("tags.txt", false), "w") { |f| f.puts @tag } + MSpec.write_tag @tag + IO.read(tmp("tags.txt", false)).should == "fail(broken):Some#method works\n" + end +end + +describe MSpec, ".delete_tag" do + before :each do + FileUtils.cp File.dirname(__FILE__) + "/tags.txt", tmp("tags.txt", false) + MSpec.stub(:tags_file).and_return(tmp("tags.txt", false)) + @tag = SpecTag.new "fail(Comments don't matter):Some#method? works" + end + + after :each do + rm_r tmp("tags.txt", false) + end + + it "deletes the tag if it exists" do + MSpec.delete_tag(@tag).should == true + IO.read(tmp("tags.txt", false)).should == %[incomplete(20%):The#best method ever +benchmark(0.01825):The#fastest method today +extended():\"Multi-line\\ntext\\ntag\" +] + end + + it "deletes a tag with escaped newlines" do + MSpec.delete_tag(SpecTag.new('extended:"Multi-line\ntext\ntag"')).should == true + IO.read(tmp("tags.txt", false)).should == %[fail(broken):Some#method? works +incomplete(20%):The#best method ever +benchmark(0.01825):The#fastest method today +] + end + + it "does not change the tags file contents if the tag doesn't exist" do + @tag.tag = "failed" + MSpec.delete_tag(@tag).should == false + IO.read(tmp("tags.txt", false)).should == %[fail(broken):Some#method? works +incomplete(20%):The#best method ever +benchmark(0.01825):The#fastest method today +extended():\"Multi-line\\ntext\\ntag\" +] + end + + it "deletes the tag file if it is empty" do + MSpec.delete_tag(@tag).should == true + MSpec.delete_tag(SpecTag.new("incomplete:The#best method ever")).should == true + MSpec.delete_tag(SpecTag.new("benchmark:The#fastest method today")).should == true + MSpec.delete_tag(SpecTag.new('extended:"Multi-line\ntext\ntag"')).should == true + File.exist?(tmp("tags.txt", false)).should == false + end +end + +describe MSpec, ".delete_tags" do + before :each do + @tags = tmp("tags.txt", false) + FileUtils.cp File.dirname(__FILE__) + "/tags.txt", @tags + MSpec.stub(:tags_file).and_return(@tags) + end + + it "deletes the tag file" do + MSpec.delete_tags + File.exist?(@tags).should be_false + end +end + +describe MSpec, ".expectation" do + it "sets the flag that an expectation has been reported" do + MSpec.clear_expectations + MSpec.expectation?.should be_false + MSpec.expectation + MSpec.expectation?.should be_true + end +end + +describe MSpec, ".expectation?" do + it "returns true if an expectation has been reported" do + MSpec.expectation + MSpec.expectation?.should be_true + end + + it "returns false if an expectation has not been reported" do + MSpec.clear_expectations + MSpec.expectation?.should be_false + end +end + +describe MSpec, ".clear_expectations" do + it "clears the flag that an expectation has been reported" do + MSpec.expectation + MSpec.expectation?.should be_true + MSpec.clear_expectations + MSpec.expectation?.should be_false + end +end + +describe MSpec, ".register_shared" do + it "stores a shared ContextState by description" do + parent = ContextState.new "container" + state = ContextState.new "shared" + state.parent = parent + prc = lambda { } + state.describe(&prc) + MSpec.register_shared(state) + MSpec.retrieve(:shared)["shared"].should == state + end +end + +describe MSpec, ".retrieve_shared" do + it "retrieves the shared ContextState matching description" do + state = ContextState.new "" + MSpec.retrieve(:shared)["shared"] = state + MSpec.retrieve_shared(:shared).should == state + end +end diff --git a/spec/mspec/spec/runner/shared_spec.rb b/spec/mspec/spec/runner/shared_spec.rb new file mode 100644 index 0000000000..791588fdca --- /dev/null +++ b/spec/mspec/spec/runner/shared_spec.rb @@ -0,0 +1,88 @@ +require 'spec_helper' +require 'mspec/runner/shared' +require 'mspec/runner/context' +require 'mspec/runner/example' + +describe Object, "#it_behaves_like" do + before :each do + ScratchPad.clear + + MSpec.setup_env + + @state = ContextState.new "Top level" + @state.instance_variable_set :@parsed, true + + @shared = ContextState.new :shared_spec, :shared => true + MSpec.stub(:retrieve_shared).and_return(@shared) + end + + it "creates @method set to the name of the aliased method" do + @shared.it("an example") { ScratchPad.record @method } + @state.it_behaves_like :shared_spec, :some_method + @state.process + ScratchPad.recorded.should == :some_method + end + + it "creates @object if the passed object" do + object = Object.new + @shared.it("an example") { ScratchPad.record @object } + @state.it_behaves_like :shared_spec, :some_method, object + @state.process + ScratchPad.recorded.should == object + end + + it "creates @object if the passed false" do + object = false + @shared.it("an example") { ScratchPad.record @object } + @state.it_behaves_like :shared_spec, :some_method, object + @state.process + ScratchPad.recorded.should == object + end + + it "sends :it_should_behave_like" do + @state.should_receive(:it_should_behave_like) + @state.it_behaves_like :shared_spec, :some_method + end + + describe "with multiple shared contexts" do + before :each do + @obj = Object.new + @obj2 = Object.new + + @state2 = ContextState.new "Second top level" + @state2.instance_variable_set :@parsed, true + end + + it "ensures the shared spec state is distinct" do + @shared.it("an example") { ScratchPad.record [@method, @object] } + + @state.it_behaves_like :shared_spec, :some_method, @obj + + @state.process + ScratchPad.recorded.should == [:some_method, @obj] + + @state2.it_behaves_like :shared_spec, :another_method, @obj2 + + @state2.process + ScratchPad.recorded.should == [:another_method, @obj2] + end + + it "ensures the shared spec state is distinct for nested shared specs" do + nested = ContextState.new "nested context" + nested.instance_variable_set :@parsed, true + nested.parent = @shared + + nested.it("another example") { ScratchPad.record [:shared, @method, @object] } + + @state.it_behaves_like :shared_spec, :some_method, @obj + + @state.process + ScratchPad.recorded.should == [:shared, :some_method, @obj] + + @state2.it_behaves_like :shared_spec, :another_method, @obj2 + + @state2.process + ScratchPad.recorded.should == [:shared, :another_method, @obj2] + end + end +end diff --git a/spec/mspec/spec/runner/tag_spec.rb b/spec/mspec/spec/runner/tag_spec.rb new file mode 100644 index 0000000000..db55a1b186 --- /dev/null +++ b/spec/mspec/spec/runner/tag_spec.rb @@ -0,0 +1,123 @@ +require 'spec_helper' +require 'mspec/runner/tag' + +describe SpecTag do + it "accepts an optional string to parse into fields" do + tag = SpecTag.new "tag(comment):description" + tag.tag.should == "tag" + tag.comment.should == "comment" + tag.description.should == "description" + end +end + +describe SpecTag, "#parse" do + before :each do + @tag = SpecTag.new + end + + it "accepts 'tag(comment):description'" do + @tag.parse "tag(I'm real):Some#method returns a value" + @tag.tag.should == "tag" + @tag.comment.should == "I'm real" + @tag.description.should == "Some#method returns a value" + end + + it "accepts 'tag:description'" do + @tag.parse "tag:Another#method" + @tag.tag.should == "tag" + @tag.comment.should == nil + @tag.description.should == "Another#method" + end + + it "accepts 'tag():description'" do + @tag.parse "tag():Another#method" + @tag.tag.should == "tag" + @tag.comment.should == nil + @tag.description.should == "Another#method" + end + + it "accepts 'tag:'" do + @tag.parse "tag:" + @tag.tag.should == "tag" + @tag.comment.should == nil + @tag.description.should == "" + end + + it "accepts 'tag(bug:555):Another#method'" do + @tag.parse "tag(bug:555):Another#method" + @tag.tag.should == "tag" + @tag.comment.should == "bug:555" + @tag.description.should == "Another#method" + end + + it "accepts 'tag(http://someplace.com/neato):Another#method'" do + @tag.parse "tag(http://someplace.com/neato):Another#method" + @tag.tag.should == "tag" + @tag.comment.should == "http://someplace.com/neato" + @tag.description.should == "Another#method" + end + + it "accepts 'tag(comment):\"Multi-line\\ntext\"'" do + @tag.parse 'tag(comment):"Multi-line\ntext"' + @tag.tag.should == "tag" + @tag.comment.should == "comment" + @tag.description.should == "Multi-line\ntext" + end + + it "ignores '#anything'" do + @tag.parse "# this could be a comment" + @tag.tag.should == nil + @tag.comment.should == nil + @tag.description.should == nil + end +end + +describe SpecTag, "#to_s" do + it "formats itself as 'tag(comment):description'" do + txt = "tag(comment):description" + tag = SpecTag.new txt + tag.tag.should == "tag" + tag.comment.should == "comment" + tag.description.should == "description" + tag.to_s.should == txt + end + + it "formats itself as 'tag:description" do + txt = "tag:description" + tag = SpecTag.new txt + tag.tag.should == "tag" + tag.comment.should == nil + tag.description.should == "description" + tag.to_s.should == txt + end + + it "formats itself as 'tag(comment):\"multi-line\\ntext\\ntag\"'" do + txt = 'tag(comment):"multi-line\ntext\ntag"' + tag = SpecTag.new txt + tag.tag.should == "tag" + tag.comment.should == "comment" + tag.description.should == "multi-line\ntext\ntag" + tag.to_s.should == txt + end +end + +describe SpecTag, "#==" do + it "returns true if the tags have the same fields" do + one = SpecTag.new "tag(this):unicorn" + two = SpecTag.new "tag(this):unicorn" + one.==(two).should == true + [one].==([two]).should == true + end +end + +describe SpecTag, "#unescape" do + it "replaces \\n by LF when the description is quoted" do + tag = SpecTag.new 'tag:"desc with\nnew line"' + tag.description.should == "desc with\nnew line" + end + + it "does not replaces \\n by LF when the description is not quoted " do + tag = SpecTag.new 'tag:desc with\nnew line' + tag.description.should == "desc with\\nnew line" + end +end diff --git a/spec/mspec/spec/runner/tags.txt b/spec/mspec/spec/runner/tags.txt new file mode 100644 index 0000000000..f4eb6ad034 --- /dev/null +++ b/spec/mspec/spec/runner/tags.txt @@ -0,0 +1,4 @@ +fail(broken):Some#method? works +incomplete(20%):The#best method ever +benchmark(0.01825):The#fastest method today +extended():"Multi-line\ntext\ntag" diff --git a/spec/mspec/spec/spec_helper.rb b/spec/mspec/spec/spec_helper.rb new file mode 100644 index 0000000000..4fae9bd7e2 --- /dev/null +++ b/spec/mspec/spec/spec_helper.rb @@ -0,0 +1,54 @@ +require 'pp' +require 'mspec/helpers/io' +require 'mspec/helpers/scratch' + +# Remove this when MRI has intelligent warnings +$VERBOSE = nil unless $VERBOSE + +class MOSConfig < Hash + def initialize + self[:loadpath] = [] + self[:requires] = [] + self[:flags] = [] + self[:options] = [] + self[:includes] = [] + self[:excludes] = [] + self[:patterns] = [] + self[:xpatterns] = [] + self[:tags] = [] + self[:xtags] = [] + self[:atags] = [] + self[:astrings] = [] + self[:target] = 'ruby' + self[:command] = nil + self[:ltags] = [] + self[:files] = [] + self[:launch] = [] + end +end + +def new_option + config = MOSConfig.new + return MSpecOptions.new("spec", 20, config), config +end + +# Just to have an exception name output not be "Exception" +class MSpecExampleError < Exception +end + +def hide_deprecation_warnings + MSpec.stub(:deprecate) +end + +def run_mspec(command, args) + cwd = Dir.pwd + command = " #{command}" unless command.start_with?('-') + cmd = "#{cwd}/bin/mspec#{command} -B spec/fixtures/config.mspec #{args}" + out = `#{cmd} 2>&1` + ret = $? + out = out.sub(/\A\$.+\n/, '') # Remove printed command line + out = out.sub(RUBY_DESCRIPTION, "RUBY_DESCRIPTION") + out = out.gsub(/\d\.\d{6}/, "D.DDDDDD") + out = out.gsub(cwd, "CWD") + return out, ret +end diff --git a/spec/mspec/spec/utils/deprecate_spec.rb b/spec/mspec/spec/utils/deprecate_spec.rb new file mode 100644 index 0000000000..14e05c6f35 --- /dev/null +++ b/spec/mspec/spec/utils/deprecate_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' +require 'mspec/utils/deprecate' + +describe MSpec, "#deprecate" do + it "warns when using a deprecated method" do + warning = nil + $stderr.stub(:puts) { |str| warning = str } + MSpec.deprecate(:some_method, :other_method) + warning.should start_with(<<-EOS.chomp) + +some_method is deprecated, use other_method instead. +from +EOS + warning.should include(__FILE__) + warning.should include('8') + end +end diff --git a/spec/mspec/spec/utils/name_map_spec.rb b/spec/mspec/spec/utils/name_map_spec.rb new file mode 100644 index 0000000000..d38230ce06 --- /dev/null +++ b/spec/mspec/spec/utils/name_map_spec.rb @@ -0,0 +1,175 @@ +require 'spec_helper' +require 'mspec/utils/name_map' + +module NameMapSpecs + class A + A = self + + def self.a; end + def a; end + def c; end + + class B + def b; end + end + end + + class Error + end + + class Fixnum + def f; end + end + + def self.n; end + def n; end +end + +describe NameMap, "#exception?" do + before :each do + @map = NameMap.new + end + + it "returns true if the constant is Errno" do + @map.exception?("Errno").should == true + end + + it "returns true if the constant is a kind of Exception" do + @map.exception?("Errno::EBADF").should == true + @map.exception?("LoadError").should == true + @map.exception?("SystemExit").should == true + end + + it "returns false if the constant is not a kind of Exception" do + @map.exception?("NameMapSpecs::Error").should == false + @map.exception?("NameMapSpecs").should == false + end + + it "returns false if the constant does not exist" do + @map.exception?("Nonexistent").should == false + end +end + +describe NameMap, "#class_or_module" do + before :each do + @map = NameMap.new true + end + + it "returns the constant specified by the string" do + @map.class_or_module("NameMapSpecs").should == NameMapSpecs + end + + it "returns the constant specified by the 'A::B' string" do + @map.class_or_module("NameMapSpecs::A").should == NameMapSpecs::A + end + + it "returns nil if the constant is not a class or module" do + @map.class_or_module("Float::MAX").should == nil + end + + it "returns nil if the constant is in the set of excluded constants" do + excluded = %w[ + MSpecScript + MkSpec + NameMap + ] + + excluded.each do |const| + @map.class_or_module(const).should == nil + end + end + + it "returns nil if the constant does not exist" do + @map.class_or_module("Heaven").should == nil + @map.class_or_module("Hell").should == nil + @map.class_or_module("Bush::Brain").should == nil + end +end + +describe NameMap, "#dir_name" do + before :each do + @map = NameMap.new + end + + it "returns a directory name from the base name and constant" do + @map.dir_name("NameMapSpecs", 'spec/core').should == 'spec/core/namemapspecs' + end + + it "returns a directory name from the components in the constants name" do + @map.dir_name("NameMapSpecs::A", 'spec').should == 'spec/namemapspecs/a' + @map.dir_name("NameMapSpecs::A::B", 'spec').should == 'spec/namemapspecs/a/b' + end + + it "returns a directory name without 'class' for constants like TrueClass" do + @map.dir_name("TrueClass", 'spec').should == 'spec/true' + @map.dir_name("FalseClass", 'spec').should == 'spec/false' + end + + it "returns 'exception' for the directory name of any Exception subclass" do + @map.dir_name("SystemExit", 'spec').should == 'spec/exception' + @map.dir_name("Errno::EBADF", 'spec').should == 'spec/exception' + end + + it "returns 'class' for Class" do + @map.dir_name("Class", 'spec').should == 'spec/class' + end +end + +# These specs do not cover all the mappings, but only describe how the +# name is derived when the hash item maps to a single value, a hash with +# a specific item, or a hash with a :default item. +describe NameMap, "#file_name" do + before :each do + @map = NameMap.new + end + + it "returns the name of the spec file based on the constant and method" do + @map.file_name("[]=", "Array").should == "element_set_spec.rb" + end + + it "returns the name of the spec file based on the special entry for the method" do + @map.file_name("~", "Regexp").should == "match_spec.rb" + @map.file_name("~", "Fixnum").should == "complement_spec.rb" + end + + it "returns the name of the spec file based on the default entry for the method" do + @map.file_name("<<", "NameMapSpecs").should == "append_spec.rb" + end + + it "uses the last component of the constant to look up the method name" do + @map.file_name("^", "NameMapSpecs::Fixnum").should == "bit_xor_spec.rb" + end +end + +describe NameMap, "#namespace" do + before :each do + @map = NameMap.new + end + + it "prepends the module to the constant name" do + @map.namespace("SubModule", Integer).should == "SubModule::Integer" + end + + it "does not prepend Object, Class, or Module to the constant name" do + @map.namespace("Object", String).should == "String" + @map.namespace("Module", Integer).should == "Integer" + @map.namespace("Class", Float).should == "Float" + end +end + +describe NameMap, "#map" do + before :each do + @map = NameMap.new + end + + it "flattens an object hierarchy into a single Hash" do + @map.map({}, [NameMapSpecs]).should == { + "NameMapSpecs." => ["n"], + "NameMapSpecs#" => ["n"], + "NameMapSpecs::A." => ["a"], + "NameMapSpecs::A#" => ["a", "c"], + "NameMapSpecs::A::B#" => ["b"], + "NameMapSpecs::Fixnum#" => ["f"] + } + end +end diff --git a/spec/mspec/spec/utils/options_spec.rb b/spec/mspec/spec/utils/options_spec.rb new file mode 100644 index 0000000000..26c52bdbd0 --- /dev/null +++ b/spec/mspec/spec/utils/options_spec.rb @@ -0,0 +1,1309 @@ +require 'spec_helper' +require 'mspec/utils/options' +require 'mspec/version' +require 'mspec/guards/guard' +require 'mspec/runner/mspec' +require 'mspec/runner/formatters' + +describe MSpecOption, ".new" do + before :each do + @opt = MSpecOption.new("-a", "--bdc", "ARG", "desc", :block) + end + + it "sets the short attribute" do + @opt.short.should == "-a" + end + + it "sets the long attribute" do + @opt.long.should == "--bdc" + end + + it "sets the arg attribute" do + @opt.arg.should == "ARG" + end + + it "sets the description attribute" do + @opt.description.should == "desc" + end + + it "sets the block attribute" do + @opt.block.should == :block + end +end + +describe MSpecOption, "#arg?" do + it "returns true if arg attribute is not nil" do + MSpecOption.new(nil, nil, "ARG", nil, nil).arg?.should be_true + end + + it "returns false if arg attribute is nil" do + MSpecOption.new(nil, nil, nil, nil, nil).arg?.should be_false + end +end + +describe MSpecOption, "#match?" do + before :each do + @opt = MSpecOption.new("-a", "--bdc", "ARG", "desc", :block) + end + + it "returns true if the argument matches the short option" do + @opt.match?("-a").should be_true + end + + it "returns true if the argument matches the long option" do + @opt.match?("--bdc").should be_true + end + + it "returns false if the argument matches neither the short nor long option" do + @opt.match?("-b").should be_false + @opt.match?("-abdc").should be_false + end +end + +describe MSpecOptions, ".new" do + before :each do + @opt = MSpecOptions.new("cmd", 20, :config) + end + + it "sets the banner attribute" do + @opt.banner.should == "cmd" + end + + it "sets the config attribute" do + @opt.config.should == :config + end + + it "sets the width attribute" do + @opt.width.should == 20 + end + + it "sets the default width attribute" do + MSpecOptions.new.width.should == 30 + end +end + +describe MSpecOptions, "#on" do + before :each do + @opt = MSpecOptions.new + end + + it "adds a short option" do + @opt.should_receive(:add).with("-a", nil, nil, "desc", nil) + @opt.on("-a", "desc") + end + + it "adds a short option taking an argument" do + @opt.should_receive(:add).with("-a", nil, "ARG", "desc", nil) + @opt.on("-a", "ARG", "desc") + end + + it "adds a long option" do + @opt.should_receive(:add).with("-a", nil, nil, "desc", nil) + @opt.on("-a", "desc") + end + + it "adds a long option taking an argument" do + @opt.should_receive(:add).with("-a", nil, nil, "desc", nil) + @opt.on("-a", "desc") + end + + it "adds a short and long option" do + @opt.should_receive(:add).with("-a", nil, nil, "desc", nil) + @opt.on("-a", "desc") + end + + it "adds a short and long option taking an argument" do + @opt.should_receive(:add).with("-a", nil, nil, "desc", nil) + @opt.on("-a", "desc") + end + + it "raises MSpecOptions::OptionError if pass less than 2 arguments" do + lambda { @opt.on }.should raise_error(MSpecOptions::OptionError) + lambda { @opt.on "" }.should raise_error(MSpecOptions::OptionError) + end +end + +describe MSpecOptions, "#add" do + before :each do + @opt = MSpecOptions.new "cmd", 20 + @prc = lambda { } + end + + it "adds documentation for an option" do + @opt.should_receive(:doc).with(" -t, --typo ARG Correct typo ARG") + @opt.add("-t", "--typo", "ARG", "Correct typo ARG", @prc) + end + + it "leaves spaces in the documentation for a missing short option" do + @opt.should_receive(:doc).with(" --typo ARG Correct typo ARG") + @opt.add(nil, "--typo", "ARG", "Correct typo ARG", @prc) + end + + it "handles a short option with argument but no long argument" do + @opt.should_receive(:doc).with(" -t ARG Correct typo ARG") + @opt.add("-t", nil, "ARG", "Correct typo ARG", @prc) + end + + it "registers an option" do + option = MSpecOption.new "-t", "--typo", "ARG", "Correct typo ARG", @prc + MSpecOption.should_receive(:new).with( + "-t", "--typo", "ARG", "Correct typo ARG", @prc).and_return(option) + @opt.add("-t", "--typo", "ARG", "Correct typo ARG", @prc) + @opt.options.should == [option] + end +end + +describe MSpecOptions, "#match?" do + before :each do + @opt = MSpecOptions.new + end + + it "returns the MSpecOption instance matching the argument" do + @opt.on "-a", "--abdc", "desc" + option = @opt.match? "-a" + @opt.match?("--abdc").should be(option) + option.should be_kind_of(MSpecOption) + option.short.should == "-a" + option.long.should == "--abdc" + option.description.should == "desc" + end +end + +describe MSpecOptions, "#process" do + before :each do + @opt = MSpecOptions.new + ScratchPad.clear + end + + it "calls the on_extra block if the argument does not match any option" do + @opt.on_extra { ScratchPad.record :extra } + @opt.process ["-a"], "-a", "-a", nil + ScratchPad.recorded.should == :extra + end + + it "returns the matching option" do + @opt.on "-a", "ARG", "desc" + option = @opt.process [], "-a", "-a", "ARG" + option.should be_kind_of(MSpecOption) + option.short.should == "-a" + option.arg.should == "ARG" + option.description.should == "desc" + end + + it "raises an MSpecOptions::ParseError if arg is nil and there are no more entries in argv" do + @opt.on "-a", "ARG", "desc" + lambda { @opt.process [], "-a", "-a", nil }.should raise_error(MSpecOptions::ParseError) + end + + it "fetches the argument for the option from argv if arg is nil" do + @opt.on("-a", "ARG", "desc") { |o| ScratchPad.record o } + @opt.process ["ARG"], "-a", "-a", nil + ScratchPad.recorded.should == "ARG" + end + + it "calls the option's block" do + @opt.on("-a", "ARG", "desc") { ScratchPad.record :option } + @opt.process [], "-a", "-a", "ARG" + ScratchPad.recorded.should == :option + end + + it "does not call the option's block if it is nil" do + @opt.on "-a", "ARG", "desc" + lambda { @opt.process [], "-a", "-a", "ARG" }.should_not raise_error + end +end + +describe MSpecOptions, "#split" do + before :each do + @opt = MSpecOptions.new + end + + it "breaks a string at the nth character" do + opt, arg, rest = @opt.split "-bdc", 2 + opt.should == "-b" + arg.should == "dc" + rest.should == "dc" + end + + it "returns nil for arg if there are no characters left" do + opt, arg, rest = @opt.split "-b", 2 + opt.should == "-b" + arg.should == nil + rest.should == "" + end +end + +describe MSpecOptions, "#parse" do + before :each do + @opt = MSpecOptions.new + @prc = lambda { ScratchPad.record :parsed } + @arg_prc = lambda { |o| ScratchPad.record [:parsed, o] } + ScratchPad.clear + end + + it "parses a short option" do + @opt.on "-a", "desc", &@prc + @opt.parse ["-a"] + ScratchPad.recorded.should == :parsed + end + + it "parse a long option" do + @opt.on "--abdc", "desc", &@prc + @opt.parse ["--abdc"] + ScratchPad.recorded.should == :parsed + end + + it "parses a short option group" do + @opt.on "-a", "ARG", "desc", &@arg_prc + @opt.parse ["-a", "ARG"] + ScratchPad.recorded.should == [:parsed, "ARG"] + end + + it "parses a short option with an argument" do + @opt.on "-a", "ARG", "desc", &@arg_prc + @opt.parse ["-a", "ARG"] + ScratchPad.recorded.should == [:parsed, "ARG"] + end + + it "parses a short option with connected argument" do + @opt.on "-a", "ARG", "desc", &@arg_prc + @opt.parse ["-aARG"] + ScratchPad.recorded.should == [:parsed, "ARG"] + end + + it "parses a long option with an argument" do + @opt.on "--abdc", "ARG", "desc", &@arg_prc + @opt.parse ["--abdc", "ARG"] + ScratchPad.recorded.should == [:parsed, "ARG"] + end + + it "parses a long option with an '=' argument" do + @opt.on "--abdc", "ARG", "desc", &@arg_prc + @opt.parse ["--abdc=ARG"] + ScratchPad.recorded.should == [:parsed, "ARG"] + end + + it "parses a short option group with the final option taking an argument" do + ScratchPad.record [] + @opt.on("-a", "desc") { |o| ScratchPad << :a } + @opt.on("-b", "ARG", "desc") { |o| ScratchPad << [:b, o] } + @opt.parse ["-ab", "ARG"] + ScratchPad.recorded.should == [:a, [:b, "ARG"]] + end + + it "parses a short option group with a connected argument" do + ScratchPad.record [] + @opt.on("-a", "desc") { |o| ScratchPad << :a } + @opt.on("-b", "ARG", "desc") { |o| ScratchPad << [:b, o] } + @opt.on("-c", "desc") { |o| ScratchPad << :c } + @opt.parse ["-acbARG"] + ScratchPad.recorded.should == [:a, :c, [:b, "ARG"]] + end + + it "returns the unprocessed entries" do + @opt.on "-a", "ARG", "desc", &@arg_prc + @opt.parse(["abdc", "-a", "ilny"]).should == ["abdc"] + end + + it "calls the on_extra handler with unrecognized options" do + ScratchPad.record [] + @opt.on_extra { |o| ScratchPad << o } + @opt.on "-a", "desc" + @opt.parse ["-a", "-b"] + ScratchPad.recorded.should == ["-b"] + end + + it "does not attempt to call the block if it is nil" do + @opt.on "-a", "ARG", "desc" + @opt.parse(["-a", "ARG"]).should == [] + end + + it "raises MSpecOptions::ParseError if passed an unrecognized option" do + @opt.should_receive(:raise).with(MSpecOptions::ParseError, an_instance_of(String)) + @opt.stub(:puts) + @opt.stub(:exit) + @opt.parse "-u" + end +end + +describe MSpecOptions, "#banner=" do + before :each do + @opt = MSpecOptions.new + end + + it "sets the banner attribute" do + @opt.banner.should == "" + @opt.banner = "banner" + @opt.banner.should == "banner" + end +end + +describe MSpecOptions, "#width=" do + before :each do + @opt = MSpecOptions.new + end + + it "sets the width attribute" do + @opt.width.should == 30 + @opt.width = 20 + @opt.width.should == 20 + end +end + +describe MSpecOptions, "#config=" do + before :each do + @opt = MSpecOptions.new + end + + it "sets the config attribute" do + @opt.config.should be_nil + @opt.config = :config + @opt.config.should == :config + end +end + +describe MSpecOptions, "#doc" do + before :each do + @opt = MSpecOptions.new "command" + end + + it "adds text to be displayed with #to_s" do + @opt.doc "Some message" + @opt.doc "Another message" + @opt.to_s.should == <<-EOD +command + +Some message +Another message +EOD + end +end + +describe MSpecOptions, "#version" do + before :each do + @opt = MSpecOptions.new + ScratchPad.clear + end + + it "installs a basic -v, --version option" do + @opt.should_receive(:puts) + @opt.should_receive(:exit) + @opt.version "1.0.0" + @opt.parse "-v" + end + + it "accepts a block instead of using the default block" do + @opt.version("1.0.0") { |o| ScratchPad.record :version } + @opt.parse "-v" + ScratchPad.recorded.should == :version + end +end + +describe MSpecOptions, "#help" do + before :each do + @opt = MSpecOptions.new + ScratchPad.clear + end + + it "installs a basic -h, --help option" do + @opt.should_receive(:puts) + @opt.should_receive(:exit).with(1) + @opt.help + @opt.parse "-h" + end + + it "accepts a block instead of using the default block" do + @opt.help { |o| ScratchPad.record :help } + @opt.parse "-h" + ScratchPad.recorded.should == :help + end +end + +describe MSpecOptions, "#on_extra" do + before :each do + @opt = MSpecOptions.new + ScratchPad.clear + end + + it "registers a block to be called when an option is not recognized" do + @opt.on_extra { ScratchPad.record :extra } + @opt.parse "-g" + ScratchPad.recorded.should == :extra + end +end + +describe MSpecOptions, "#to_s" do + before :each do + @opt = MSpecOptions.new "command" + end + + it "returns the banner and descriptive strings for all registered options" do + @opt.on "-t", "--this ARG", "Adds this ARG to the list" + @opt.to_s.should == <<-EOD +command + + -t, --this ARG Adds this ARG to the list +EOD + end +end + +describe "The -B, --config FILE option" do + before :each do + @options, @config = new_option + end + + it "is enabled with #configure { }" do + @options.should_receive(:on).with("-B", "--config", "FILE", + an_instance_of(String)) + @options.configure {} + end + + it "calls the passed block" do + ["-B", "--config"].each do |opt| + ScratchPad.clear + + @options.configure { |x| ScratchPad.record x } + @options.parse [opt, "file"] + ScratchPad.recorded.should == "file" + end + end +end + +describe "The -C, --chdir DIR option" do + before :each do + @options, @config = new_option + @options.chdir + end + + it "is enabled with #chdir" do + @options.should_receive(:on).with("-C", "--chdir", "DIR", + an_instance_of(String)) + @options.chdir + end + + it "changes the working directory to DIR" do + Dir.should_receive(:chdir).with("dir").twice + ["-C", "--chdir"].each do |opt| + @options.parse [opt, "dir"] + end + end +end + +describe "The --prefix STR option" do + before :each do + @options, @config = new_option + end + + it "is enabled with #prefix" do + @options.should_receive(:on).with("--prefix", "STR", + an_instance_of(String)) + @options.prefix + end + + it "sets the prefix config value" do + @options.prefix + @options.parse ["--prefix", "some/dir"] + @config[:prefix].should == "some/dir" + end +end + +describe "The -n, --name RUBY_NAME option" do + before :each do + @verbose, $VERBOSE = $VERBOSE, nil + @options, @config = new_option + end + + after :each do + $VERBOSE = @verbose + end + + it "is enabled with #name" do + @options.should_receive(:on).with("-n", "--name", "RUBY_NAME", + an_instance_of(String)) + @options.name + end + + it "sets RUBY_NAME when invoked" do + Object.should_receive(:const_set).with(:RUBY_NAME, "name").twice + @options.name + @options.parse ["-n", "name"] + @options.parse ["--name", "name"] + end +end + +describe "The -t, --target TARGET option" do + before :each do + @options, @config = new_option + @options.targets + end + + it "is enabled with #targets" do + @options.stub(:on) + @options.should_receive(:on).with("-t", "--target", "TARGET", + an_instance_of(String)) + @options.targets + end + + it "sets the target to 'ruby' and flags to verbose with TARGET 'r' or 'ruby'" do + ["-t", "--target"].each do |opt| + ["r", "ruby"].each do |t| + @config[:target] = nil + @options.parse [opt, t] + @config[:target].should == "ruby" + end + end + end + + it "sets the target to 'jruby' with TARGET 'j' or 'jruby'" do + ["-t", "--target"].each do |opt| + ["j", "jruby"].each do |t| + @config[:target] = nil + @options.parse [opt, t] + @config[:target].should == "jruby" + end + end + end + + it "sets the target to 'shotgun/rubinius' with TARGET 'x' or 'rubinius'" do + ["-t", "--target"].each do |opt| + ["x", "rubinius"].each do |t| + @config[:target] = nil + @options.parse [opt, t] + @config[:target].should == "./bin/rbx" + end + end + end + + it "set the target to 'rbx' with TARGET 'rbx'" do + ["-t", "--target"].each do |opt| + ["X", "rbx"].each do |t| + @config[:target] = nil + @options.parse [opt, t] + @config[:target].should == "rbx" + end + end + end + + it "sets the target to 'maglev' with TARGET 'm' or 'maglev'" do + ["-t", "--target"].each do |opt| + ["m", "maglev"].each do |t| + @config[:target] = nil + @options.parse [opt, t] + @config[:target].should == "maglev-ruby" + end + end + end + + it "sets the target to 'topaz' with TARGET 't' or 'topaz'" do + ["-t", "--target"].each do |opt| + ["t", "topaz"].each do |t| + @config[:target] = nil + @options.parse [opt, t] + @config[:target].should == "topaz" + end + end + end + + it "sets the target to TARGET" do + ["-t", "--target"].each do |opt| + @config[:target] = nil + @options.parse [opt, "whateva"] + @config[:target].should == "whateva" + end + end +end + +describe "The -T, --target-opt OPT option" do + before :each do + @options, @config = new_option + @options.targets + end + + it "is enabled with #targets" do + @options.stub(:on) + @options.should_receive(:on).with("-T", "--target-opt", "OPT", + an_instance_of(String)) + @options.targets + end + + it "adds OPT to flags" do + ["-T", "--target-opt"].each do |opt| + @config[:flags].delete "--whateva" + @options.parse [opt, "--whateva"] + @config[:flags].should include("--whateva") + end + end +end + +describe "The -I, --include DIR option" do + before :each do + @options, @config = new_option + @options.targets + end + + it "is enabled with #targets" do + @options.stub(:on) + @options.should_receive(:on).with("-I", "--include", "DIR", + an_instance_of(String)) + @options.targets + end + + it "add DIR to the load path" do + ["-I", "--include"].each do |opt| + @config[:loadpath].delete "-Ipackage" + @options.parse [opt, "package"] + @config[:loadpath].should include("-Ipackage") + end + end +end + +describe "The -r, --require LIBRARY option" do + before :each do + @options, @config = new_option + @options.targets + end + + it "is enabled with #targets" do + @options.stub(:on) + @options.should_receive(:on).with("-r", "--require", "LIBRARY", + an_instance_of(String)) + @options.targets + end + + it "adds LIBRARY to the requires list" do + ["-r", "--require"].each do |opt| + @config[:requires].delete "-rlibrick" + @options.parse [opt, "librick"] + @config[:requires].should include("-rlibrick") + end + end +end + +describe "The -f, --format FORMAT option" do + before :each do + @options, @config = new_option + @options.formatters + end + + it "is enabled with #formatters" do + @options.stub(:on) + @options.should_receive(:on).with("-f", "--format", "FORMAT", + an_instance_of(String)) + @options.formatters + end + + it "sets the SpecdocFormatter with FORMAT 's' or 'specdoc'" do + ["-f", "--format"].each do |opt| + ["s", "specdoc"].each do |f| + @config[:formatter] = nil + @options.parse [opt, f] + @config[:formatter].should == SpecdocFormatter + end + end + end + + it "sets the HtmlFormatter with FORMAT 'h' or 'html'" do + ["-f", "--format"].each do |opt| + ["h", "html"].each do |f| + @config[:formatter] = nil + @options.parse [opt, f] + @config[:formatter].should == HtmlFormatter + end + end + end + + it "sets the DottedFormatter with FORMAT 'd', 'dot' or 'dotted'" do + ["-f", "--format"].each do |opt| + ["d", "dot", "dotted"].each do |f| + @config[:formatter] = nil + @options.parse [opt, f] + @config[:formatter].should == DottedFormatter + end + end + end + + it "sets the DescribeFormatter with FORMAT 'b' or 'describe'" do + ["-f", "--format"].each do |opt| + ["b", "describe"].each do |f| + @config[:formatter] = nil + @options.parse [opt, f] + @config[:formatter].should == DescribeFormatter + end + end + end + + it "sets the FileFormatter with FORMAT 'f', 'file'" do + ["-f", "--format"].each do |opt| + ["f", "file"].each do |f| + @config[:formatter] = nil + @options.parse [opt, f] + @config[:formatter].should == FileFormatter + end + end + end + + it "sets the UnitdiffFormatter with FORMAT 'u', 'unit', or 'unitdiff'" do + ["-f", "--format"].each do |opt| + ["u", "unit", "unitdiff"].each do |f| + @config[:formatter] = nil + @options.parse [opt, f] + @config[:formatter].should == UnitdiffFormatter + end + end + end + + it "sets the SummaryFormatter with FORMAT 'm' or 'summary'" do + ["-f", "--format"].each do |opt| + ["m", "summary"].each do |f| + @config[:formatter] = nil + @options.parse [opt, f] + @config[:formatter].should == SummaryFormatter + end + end + end + + it "sets the SpinnerFormatter with FORMAT 'a', '*', or 'spin'" do + ["-f", "--format"].each do |opt| + ["a", "*", "spin"].each do |f| + @config[:formatter] = nil + @options.parse [opt, f] + @config[:formatter].should == SpinnerFormatter + end + end + end + + it "sets the MethodFormatter with FORMAT 't' or 'method'" do + ["-f", "--format"].each do |opt| + ["t", "method"].each do |f| + @config[:formatter] = nil + @options.parse [opt, f] + @config[:formatter].should == MethodFormatter + end + end + end + + it "sets the YamlFormatter with FORMAT 'y' or 'yaml'" do + ["-f", "--format"].each do |opt| + ["y", "yaml"].each do |f| + @config[:formatter] = nil + @options.parse [opt, f] + @config[:formatter].should == YamlFormatter + end + end + end + + it "sets the JUnitFormatter with FORMAT 'j' or 'junit'" do + ["-f", "--format"].each do |opt| + ["j", "junit"].each do |f| + @config[:formatter] = nil + @options.parse [opt, f] + @config[:formatter].should == JUnitFormatter + end + end + end +end + +describe "The -o, --output FILE option" do + before :each do + @options, @config = new_option + @options.formatters + end + + it "is enabled with #formatters" do + @options.stub(:on) + @options.should_receive(:on).with("-o", "--output", "FILE", + an_instance_of(String)) + @options.formatters + end + + it "sets the output to FILE" do + ["-o", "--output"].each do |opt| + @config[:output] = nil + @options.parse [opt, "some/file"] + @config[:output].should == "some/file" + end + end +end + +describe "The -e, --example STR" do + before :each do + @options, @config = new_option + @options.filters + end + + it "is enabled with #filters" do + @options.stub(:on) + @options.should_receive(:on).with("-e", "--example", "STR", + an_instance_of(String)) + @options.filters + end + + it "adds STR to the includes list" do + ["-e", "--example"].each do |opt| + @config[:includes] = [] + @options.parse [opt, "this spec"] + @config[:includes].should include("this spec") + end + end +end + +describe "The -E, --exclude STR" do + before :each do + @options, @config = new_option + @options.filters + end + + it "is enabled with #filters" do + @options.stub(:on) + @options.should_receive(:on).with("-E", "--exclude", "STR", + an_instance_of(String)) + @options.filters + end + + it "adds STR to the excludes list" do + ["-E", "--exclude"].each do |opt| + @config[:excludes] = [] + @options.parse [opt, "this spec"] + @config[:excludes].should include("this spec") + end + end +end + +describe "The -p, --pattern PATTERN" do + before :each do + @options, @config = new_option + @options.filters + end + + it "is enabled with #filters" do + @options.stub(:on) + @options.should_receive(:on).with("-p", "--pattern", "PATTERN", + an_instance_of(String)) + @options.filters + end + + it "adds PATTERN to the included patterns list" do + ["-p", "--pattern"].each do |opt| + @config[:patterns] = [] + @options.parse [opt, "this spec"] + @config[:patterns].should include(/this spec/) + end + end +end + +describe "The -P, --excl-pattern PATTERN" do + before :each do + @options, @config = new_option + @options.filters + end + + it "is enabled with #filters" do + @options.stub(:on) + @options.should_receive(:on).with("-P", "--excl-pattern", "PATTERN", + an_instance_of(String)) + @options.filters + end + + it "adds PATTERN to the excluded patterns list" do + ["-P", "--excl-pattern"].each do |opt| + @config[:xpatterns] = [] + @options.parse [opt, "this spec"] + @config[:xpatterns].should include(/this spec/) + end + end +end + +describe "The -g, --tag TAG" do + before :each do + @options, @config = new_option + @options.filters + end + + it "is enabled with #filters" do + @options.stub(:on) + @options.should_receive(:on).with("-g", "--tag", "TAG", + an_instance_of(String)) + @options.filters + end + + it "adds TAG to the included tags list" do + ["-g", "--tag"].each do |opt| + @config[:tags] = [] + @options.parse [opt, "this spec"] + @config[:tags].should include("this spec") + end + end +end + +describe "The -G, --excl-tag TAG" do + before :each do + @options, @config = new_option + @options.filters + end + + it "is enabled with #filters" do + @options.stub(:on) + @options.should_receive(:on).with("-G", "--excl-tag", "TAG", + an_instance_of(String)) + @options.filters + end + + it "adds TAG to the excluded tags list" do + ["-G", "--excl-tag"].each do |opt| + @config[:xtags] = [] + @options.parse [opt, "this spec"] + @config[:xtags].should include("this spec") + end + end +end + +describe "The -w, --profile FILE option" do + before :each do + @options, @config = new_option + @options.filters + end + + it "is enabled with #filters" do + @options.stub(:on) + @options.should_receive(:on).with("-w", "--profile", "FILE", + an_instance_of(String)) + @options.filters + end + + it "adds FILE to the included profiles list" do + ["-w", "--profile"].each do |opt| + @config[:profiles] = [] + @options.parse [opt, "spec/profiles/rails.yaml"] + @config[:profiles].should include("spec/profiles/rails.yaml") + end + end +end + +describe "The -W, --excl-profile FILE option" do + before :each do + @options, @config = new_option + @options.filters + end + + it "is enabled with #filters" do + @options.stub(:on) + @options.should_receive(:on).with("-W", "--excl-profile", "FILE", + an_instance_of(String)) + @options.filters + end + + it "adds FILE to the excluded profiles list" do + ["-W", "--excl-profile"].each do |opt| + @config[:xprofiles] = [] + @options.parse [opt, "spec/profiles/rails.yaml"] + @config[:xprofiles].should include("spec/profiles/rails.yaml") + end + end +end + +describe "The -Z, --dry-run option" do + before :each do + @options, @config = new_option + @options.pretend + end + + it "is enabled with #pretend" do + @options.should_receive(:on).with("-Z", "--dry-run", an_instance_of(String)) + @options.pretend + end + + it "registers the MSpec pretend mode" do + MSpec.should_receive(:register_mode).with(:pretend).twice + ["-Z", "--dry-run"].each do |opt| + @options.parse opt + end + end +end + +describe "The --unguarded option" do + before :each do + @options, @config = new_option + @options.unguarded + end + + it "is enabled with #unguarded" do + @options.stub(:on) + @options.should_receive(:on).with("--unguarded", an_instance_of(String)) + @options.unguarded + end + + it "registers the MSpec unguarded mode" do + MSpec.should_receive(:register_mode).with(:unguarded) + @options.parse "--unguarded" + end +end + +describe "The --no-ruby_guard option" do + before :each do + @options, @config = new_option + @options.unguarded + end + + it "is enabled with #unguarded" do + @options.stub(:on) + @options.should_receive(:on).with("--no-ruby_bug", an_instance_of(String)) + @options.unguarded + end + + it "registers the MSpec no_ruby_bug mode" do + MSpec.should_receive(:register_mode).with(:no_ruby_bug) + @options.parse "--no-ruby_bug" + end +end + +describe "The -H, --random option" do + before :each do + @options, @config = new_option + @options.randomize + end + + it "is enabled with #randomize" do + @options.should_receive(:on).with("-H", "--random", an_instance_of(String)) + @options.randomize + end + + it "registers the MSpec randomize mode" do + MSpec.should_receive(:randomize).twice + ["-H", "--random"].each do |opt| + @options.parse opt + end + end +end + +describe "The -R, --repeat option" do + before :each do + @options, @config = new_option + @options.repeat + end + + it "is enabled with #repeat" do + @options.should_receive(:on).with("-R", "--repeat", "NUMBER", an_instance_of(String)) + @options.repeat + end + + it "registers the MSpec repeat mode" do + ["-R", "--repeat"].each do |opt| + MSpec.repeat = 1 + @options.parse [opt, "10"] + repeat_count = 0 + MSpec.repeat do + repeat_count += 1 + end + repeat_count.should == 10 + end + end +end + +describe "The -V, --verbose option" do + before :each do + @options, @config = new_option + @options.verbose + end + + it "is enabled with #verbose" do + @options.stub(:on) + @options.should_receive(:on).with("-V", "--verbose", an_instance_of(String)) + @options.verbose + end + + it "registers a verbose output object with MSpec" do + MSpec.should_receive(:register).with(:start, anything()).twice + MSpec.should_receive(:register).with(:load, anything()).twice + ["-V", "--verbose"].each do |opt| + @options.parse opt + end + end +end + +describe "The -m, --marker MARKER option" do + before :each do + @options, @config = new_option + @options.verbose + end + + it "is enabled with #verbose" do + @options.stub(:on) + @options.should_receive(:on).with("-m", "--marker", "MARKER", + an_instance_of(String)) + @options.verbose + end + + it "registers a marker output object with MSpec" do + MSpec.should_receive(:register).with(:load, anything()).twice + ["-m", "--marker"].each do |opt| + @options.parse [opt, ","] + end + end +end + +describe "The --int-spec option" do + before :each do + @options, @config = new_option + @options.interrupt + end + + it "is enabled with #interrupt" do + @options.should_receive(:on).with("--int-spec", an_instance_of(String)) + @options.interrupt + end + + it "sets the abort config option to false to only abort the running spec with ^C" do + @config[:abort] = true + @options.parse "--int-spec" + @config[:abort].should == false + end +end + +describe "The -Y, --verify option" do + before :each do + @options, @config = new_option + @options.verify + end + + it "is enabled with #interrupt" do + @options.stub(:on) + @options.should_receive(:on).with("-Y", "--verify", an_instance_of(String)) + @options.verify + end + + it "sets the MSpec mode to :verify" do + MSpec.should_receive(:register_mode).with(:verify).twice + ["-Y", "--verify"].each do |m| + @options.parse m + end + end +end + +describe "The -O, --report option" do + before :each do + @options, @config = new_option + @options.verify + end + + it "is enabled with #interrupt" do + @options.stub(:on) + @options.should_receive(:on).with("-O", "--report", an_instance_of(String)) + @options.verify + end + + it "sets the MSpec mode to :report" do + MSpec.should_receive(:register_mode).with(:report).twice + ["-O", "--report"].each do |m| + @options.parse m + end + end +end + +describe "The --report-on GUARD option" do + before :all do + MSpec.stub(:register_mode) + end + + before :each do + @options, @config = new_option + @options.verify + + SpecGuard.clear_guards + end + + after :each do + SpecGuard.clear_guards + end + + it "is enabled with #interrupt" do + @options.stub(:on) + @options.should_receive(:on).with("--report-on", "GUARD", an_instance_of(String)) + @options.verify + end + + it "sets the MSpec mode to :report_on" do + MSpec.should_receive(:register_mode).with(:report_on) + @options.parse ["--report-on", "ruby_bug"] + end + + it "converts the guard name to a symbol" do + name = double("ruby_bug") + name.should_receive(:to_sym) + @options.parse ["--report-on", name] + end + + it "saves the name of the guard" do + @options.parse ["--report-on", "ruby_bug"] + SpecGuard.guards.should == [:ruby_bug] + end +end + +describe "The -K, --action-tag TAG option" do + before :each do + @options, @config = new_option + @options.action_filters + end + + it "is enabled with #action_filters" do + @options.stub(:on) + @options.should_receive(:on).with("-K", "--action-tag", "TAG", + an_instance_of(String)) + @options.action_filters + end + + it "adds TAG to the list of tags that trigger actions" do + ["-K", "--action-tag"].each do |opt| + @config[:atags] = [] + @options.parse [opt, "action-tag"] + @config[:atags].should include("action-tag") + end + end +end + +describe "The -S, --action-string STR option" do + before :each do + @options, @config = new_option + @options.action_filters + end + + it "is enabled with #action_filters" do + @options.stub(:on) + @options.should_receive(:on).with("-S", "--action-string", "STR", + an_instance_of(String)) + @options.action_filters + end + + it "adds STR to the list of spec descriptions that trigger actions" do + ["-S", "--action-string"].each do |opt| + @config[:astrings] = [] + @options.parse [opt, "action-str"] + @config[:astrings].should include("action-str") + end + end +end + +describe "The -d, --debug option" do + before :each do + @options, @config = new_option + @options.debug + end + + after :each do + $MSPEC_DEBUG = nil + end + + it "is enabled with #debug" do + @options.stub(:on) + @options.should_receive(:on).with("-d", "--debug", an_instance_of(String)) + @options.debug + end + + it "sets $MSPEC_DEBUG to true" do + ["-d", "--debug"].each do |opt| + $MSPEC_DEBUG.should_not be_true + @options.parse opt + $MSPEC_DEBUG.should be_true + $MSPEC_DEBUG = nil + end + end +end diff --git a/spec/mspec/spec/utils/script_spec.rb b/spec/mspec/spec/utils/script_spec.rb new file mode 100644 index 0000000000..62f6787acd --- /dev/null +++ b/spec/mspec/spec/utils/script_spec.rb @@ -0,0 +1,473 @@ +require 'spec_helper' +require 'mspec/utils/script' +require 'mspec/runner/mspec' +require 'mspec/runner/filters' +require 'mspec/runner/actions/filter' + +describe MSpecScript, ".config" do + it "returns a Hash" do + MSpecScript.config.should be_kind_of(Hash) + end +end + +describe MSpecScript, ".set" do + it "sets the config hash key, value" do + MSpecScript.set :a, 10 + MSpecScript.config[:a].should == 10 + end +end + +describe MSpecScript, ".get" do + it "gets the config hash value for a key" do + MSpecScript.set :a, 10 + MSpecScript.get(:a).should == 10 + end +end + +describe MSpecScript, "#config" do + it "returns the MSpecScript config hash" do + MSpecScript.set :b, 5 + MSpecScript.new.config[:b].should == 5 + end + + it "returns the MSpecScript config hash from subclasses" do + class MSSClass < MSpecScript; end + MSpecScript.set :b, 5 + MSSClass.new.config[:b].should == 5 + end +end + +describe MSpecScript, "#load_default" do + before :all do + @verbose = $VERBOSE + $VERBOSE = nil + end + + after :all do + $VERBOSE = @verbose + end + + before :each do + @version = RUBY_VERSION + if Object.const_defined? :RUBY_ENGINE + @engine = Object.const_get :RUBY_ENGINE + end + @script = MSpecScript.new + MSpecScript.stub(:new).and_return(@script) + end + + after :each do + Object.const_set :RUBY_VERSION, @version + Object.const_set :RUBY_ENGINE, @engine if @engine + end + + it "attempts to load 'default.mspec'" do + @script.stub(:try_load) + @script.should_receive(:try_load).with('default.mspec').and_return(true) + @script.load_default + end + + it "attempts to load a config file based on RUBY_ENGINE and RUBY_VERSION" do + Object.const_set :RUBY_ENGINE, "ybur" + Object.const_set :RUBY_VERSION, "1.8.9" + default = "ybur.1.8.mspec" + @script.should_receive(:try_load).with('default.mspec').and_return(false) + @script.should_receive(:try_load).with(default) + @script.should_receive(:try_load).with('ybur.mspec') + @script.load_default + end +end + +describe MSpecScript, ".main" do + before :each do + @script = double("MSpecScript").as_null_object + MSpecScript.stub(:new).and_return(@script) + # Do not require full mspec as it would conflict with RSpec + MSpecScript.should_receive(:require).with('mspec') + end + + it "creates an instance of MSpecScript" do + MSpecScript.should_receive(:new).and_return(@script) + MSpecScript.main + end + + it "attempts to load the default config" do + @script.should_receive(:load_default) + MSpecScript.main + end + + it "attempts to load the '~/.mspecrc' script" do + @script.should_receive(:try_load).with('~/.mspecrc') + MSpecScript.main + end + + it "calls the #options method on the script" do + @script.should_receive(:options) + MSpecScript.main + end + + it "calls the #signals method on the script" do + @script.should_receive(:signals) + MSpecScript.main + end + + it "calls the #register method on the script" do + @script.should_receive(:register) + MSpecScript.main + end + + it "calls the #setup_env method on the script" do + @script.should_receive(:setup_env) + MSpecScript.main + end + + it "calls the #run method on the script" do + @script.should_receive(:run) + MSpecScript.main + end +end + +describe MSpecScript, "#initialize" do + before :each do + @config = MSpecScript.new.config + end + + it "sets the default config values" do + @config[:formatter].should == nil + @config[:includes].should == [] + @config[:excludes].should == [] + @config[:patterns].should == [] + @config[:xpatterns].should == [] + @config[:tags].should == [] + @config[:xtags].should == [] + @config[:atags].should == [] + @config[:astrings].should == [] + @config[:abort].should == true + @config[:config_ext].should == '.mspec' + end +end + +describe MSpecScript, "#load" do + before :each do + File.stub(:exist?).and_return(false) + @script = MSpecScript.new + @file = "default.mspec" + @base = "default" + end + + it "attempts to locate the file through the expanded path name" do + File.should_receive(:expand_path).with(@file, ".").and_return(@file) + File.should_receive(:exist?).with(@file).and_return(true) + Kernel.should_receive(:load).with(@file).and_return(:loaded) + @script.load(@file).should == :loaded + end + + it "appends config[:config_ext] to the name and attempts to locate the file through the expanded path name" do + File.should_receive(:expand_path).with(@base, ".").and_return(@base) + File.should_receive(:expand_path).with(@base, "spec").and_return(@base) + File.should_receive(:expand_path).with(@file, ".").and_return(@file) + File.should_receive(:exist?).with(@base).and_return(false) + File.should_receive(:exist?).with(@file).and_return(true) + Kernel.should_receive(:load).with(@file).and_return(:loaded) + @script.load(@base).should == :loaded + end + + it "attemps to locate the file in '.'" do + path = File.expand_path @file, "." + File.should_receive(:exist?).with(path).and_return(true) + Kernel.should_receive(:load).with(path).and_return(:loaded) + @script.load(@file).should == :loaded + end + + it "appends config[:config_ext] to the name and attempts to locate the file in '.'" do + path = File.expand_path @file, "." + File.should_receive(:exist?).with(path).and_return(true) + Kernel.should_receive(:load).with(path).and_return(:loaded) + @script.load(@base).should == :loaded + end + + it "attemps to locate the file in 'spec'" do + path = File.expand_path @file, "spec" + File.should_receive(:exist?).with(path).and_return(true) + Kernel.should_receive(:load).with(path).and_return(:loaded) + @script.load(@file).should == :loaded + end + + it "appends config[:config_ext] to the name and attempts to locate the file in 'spec'" do + path = File.expand_path @file, "spec" + File.should_receive(:exist?).with(path).and_return(true) + Kernel.should_receive(:load).with(path).and_return(:loaded) + @script.load(@base).should == :loaded + end + + it "loads a given file only once" do + path = File.expand_path @file, "spec" + File.should_receive(:exist?).with(path).and_return(true) + Kernel.should_receive(:load).once.with(path).and_return(:loaded) + @script.load(@base).should == :loaded + @script.load(@base).should == true + end +end + +describe MSpecScript, "#custom_options" do + before :each do + @script = MSpecScript.new + end + + after :each do + end + + it "prints 'None'" do + options = double("options") + options.should_receive(:doc).with(" No custom options registered") + @script.custom_options options + end +end + +describe MSpecScript, "#register" do + before :each do + @script = MSpecScript.new + + @formatter = double("formatter").as_null_object + @script.config[:formatter] = @formatter + end + + it "creates and registers the formatter" do + @formatter.should_receive(:new).and_return(@formatter) + @formatter.should_receive(:register) + @script.register + end + + it "does not register the formatter if config[:formatter] is false" do + @script.config[:formatter] = false + @script.register + end + + it "calls #custom_register" do + @script.should_receive(:custom_register) + @script.register + end + + it "registers :formatter with the formatter instance" do + @formatter.stub(:new).and_return(@formatter) + MSpec.should_receive(:store).with(:formatter, @formatter) + @script.register + end + + it "does not register :formatter if config[:formatter] is false" do + @script.config[:formatter] = false + MSpec.should_not_receive(:store) + @script.register + end +end + +describe MSpecScript, "#register" do + before :each do + @script = MSpecScript.new + + @formatter = double("formatter").as_null_object + @script.config[:formatter] = @formatter + + @filter = double("filter") + @filter.should_receive(:register) + + @ary = ["some", "spec"] + end + + it "creates and registers a MatchFilter for include specs" do + MatchFilter.should_receive(:new).with(:include, *@ary).and_return(@filter) + @script.config[:includes] = @ary + @script.register + end + + it "creates and registers a MatchFilter for excluded specs" do + MatchFilter.should_receive(:new).with(:exclude, *@ary).and_return(@filter) + @script.config[:excludes] = @ary + @script.register + end + + it "creates and registers a RegexpFilter for include specs" do + RegexpFilter.should_receive(:new).with(:include, *@ary).and_return(@filter) + @script.config[:patterns] = @ary + @script.register + end + + it "creates and registers a RegexpFilter for excluded specs" do + RegexpFilter.should_receive(:new).with(:exclude, *@ary).and_return(@filter) + @script.config[:xpatterns] = @ary + @script.register + end + + it "creates and registers a TagFilter for include specs" do + TagFilter.should_receive(:new).with(:include, *@ary).and_return(@filter) + @script.config[:tags] = @ary + @script.register + end + + it "creates and registers a TagFilter for excluded specs" do + TagFilter.should_receive(:new).with(:exclude, *@ary).and_return(@filter) + @script.config[:xtags] = @ary + @script.register + end + + it "creates and registers a ProfileFilter for include specs" do + ProfileFilter.should_receive(:new).with(:include, *@ary).and_return(@filter) + @script.config[:profiles] = @ary + @script.register + end + + it "creates and registers a ProfileFilter for excluded specs" do + ProfileFilter.should_receive(:new).with(:exclude, *@ary).and_return(@filter) + @script.config[:xprofiles] = @ary + @script.register + end +end + +describe MSpecScript, "#signals" do + before :each do + @script = MSpecScript.new + @abort = @script.config[:abort] + end + + after :each do + @script.config[:abort] = @abort + end + + it "traps the INT signal if config[:abort] is true" do + Signal.should_receive(:trap).with("INT") + @script.config[:abort] = true + @script.signals + end + + it "does not trap the INT signal if config[:abort] is not true" do + Signal.should_not_receive(:trap).with("INT") + @script.config[:abort] = false + @script.signals + end +end + +describe MSpecScript, "#entries" do + before :each do + @script = MSpecScript.new + + File.stub(:expand_path).and_return("name") + File.stub(:file?).and_return(false) + File.stub(:directory?).and_return(false) + end + + it "returns the pattern in an array if it is a file" do + File.should_receive(:expand_path).with("file").and_return("file/expanded") + File.should_receive(:file?).with("file/expanded").and_return(true) + @script.entries("file").should == ["file/expanded"] + end + + it "returns Dir['pattern/**/*_spec.rb'] if pattern is a directory" do + File.should_receive(:directory?).with("name").and_return(true) + File.stub(:expand_path).and_return("name","name/**/*_spec.rb") + Dir.should_receive(:[]).with("name/**/*_spec.rb").and_return(["dir1", "dir2"]) + @script.entries("name").should == ["dir1", "dir2"] + end + + it "aborts if pattern cannot be resolved to a file nor a directory" do + @script.should_receive(:abort) + @script.entries("pattern") + end + + describe "with config[:prefix] set" do + before :each do + prefix = "prefix/dir" + @script.config[:prefix] = prefix + @name = prefix + "/name" + end + + it "returns the pattern in an array if it is a file" do + File.should_receive(:expand_path).with(@name).and_return(@name) + File.should_receive(:file?).with(@name).and_return(true) + @script.entries("name").should == [@name] + end + + it "returns Dir['pattern/**/*_spec.rb'] if pattern is a directory" do + File.stub(:expand_path).and_return(@name, @name+"/**/*_spec.rb") + File.should_receive(:directory?).with(@name).and_return(true) + Dir.should_receive(:[]).with(@name + "/**/*_spec.rb").and_return(["dir1", "dir2"]) + @script.entries("name").should == ["dir1", "dir2"] + end + + it "aborts if pattern cannot be resolved to a file nor a directory" do + @script.should_receive(:abort) + @script.entries("pattern") + end + end +end + +describe MSpecScript, "#files" do + before :each do + @script = MSpecScript.new + end + + it "accumlates the values returned by #entries" do + @script.should_receive(:entries).and_return(["file1"], ["file2"]) + @script.files(["a", "b"]).should == ["file1", "file2"] + end + + it "strips a leading '^' and removes the values returned by #entries" do + @script.should_receive(:entries).and_return(["file1"], ["file2"], ["file1"]) + @script.files(["a", "b", "^a"]).should == ["file2"] + end + + it "processes the array elements in order" do + @script.should_receive(:entries).and_return(["file1"], ["file1"], ["file2"]) + @script.files(["^a", "a", "b"]).should == ["file1", "file2"] + end +end + +describe MSpecScript, "#files" do + before :each do + MSpecScript.set :files, ["file1", "file2"] + + @script = MSpecScript.new + end + + after :each do + MSpecScript.config.delete :files + end + + it "looks up items with leading ':' in the config object" do + @script.should_receive(:entries).and_return(["file1"], ["file2"]) + @script.files([":files"]).should == ["file1", "file2"] + end + + it "returns an empty list if the config key is not set" do + @script.files([":all_files"]).should == [] + end +end + +describe MSpecScript, "#setup_env" do + before :each do + @script = MSpecScript.new + @options, @config = new_option + @script.stub(:config).and_return(@config) + end + + after :each do + end + + it "sets MSPEC_RUNNER = '1' in the environment" do + ENV["MSPEC_RUNNER"] = "0" + @script.setup_env + ENV["MSPEC_RUNNER"].should == "1" + end + + it "sets RUBY_EXE = config[:target] in the environment" do + ENV["RUBY_EXE"] = nil + @script.setup_env + ENV["RUBY_EXE"].should == @config[:target] + end + + it "sets RUBY_FLAGS = config[:flags] in the environment" do + ENV["RUBY_FLAGS"] = nil + @config[:flags] = ["-w", "-Q"] + @script.setup_env + ENV["RUBY_FLAGS"].should == "-w -Q" + end +end diff --git a/spec/mspec/spec/utils/version_spec.rb b/spec/mspec/spec/utils/version_spec.rb new file mode 100644 index 0000000000..0b2d383c6d --- /dev/null +++ b/spec/mspec/spec/utils/version_spec.rb @@ -0,0 +1,45 @@ +require 'spec_helper' +require 'mspec/utils/version' + +describe SpecVersion, "#to_s" do + it "returns the string with which it was initialized" do + SpecVersion.new("1.8").to_s.should == "1.8" + SpecVersion.new("2.118.9").to_s.should == "2.118.9" + end +end + +describe SpecVersion, "#to_str" do + it "returns the same string as #to_s" do + version = SpecVersion.new("2.118.9") + version.to_str.should == version.to_s + end +end + +describe SpecVersion, "#to_i with ceil = false" do + it "returns an integer representation of the version string" do + SpecVersion.new("2.23.10").to_i.should == 1022310 + end + + it "replaces missing version parts with zeros" do + SpecVersion.new("1.8").to_i.should == 1010800 + SpecVersion.new("1.8.6").to_i.should == 1010806 + end +end + +describe SpecVersion, "#to_i with ceil = true" do + it "returns an integer representation of the version string" do + SpecVersion.new("1.8.6", true).to_i.should == 1010806 + end + + it "fills in 9s for missing tiny values" do + SpecVersion.new("1.8", true).to_i.should == 1010899 + SpecVersion.new("1.8.6", true).to_i.should == 1010806 + end +end + +describe SpecVersion, "#to_int" do + it "returns the same value as #to_i" do + version = SpecVersion.new("4.16.87") + version.to_int.should == version.to_i + end +end diff --git a/spec/mspec/tool/remove_old_guards.rb b/spec/mspec/tool/remove_old_guards.rb new file mode 100644 index 0000000000..d0920344eb --- /dev/null +++ b/spec/mspec/tool/remove_old_guards.rb @@ -0,0 +1,41 @@ +# Remove old version guards in ruby/spec + +def dedent(line) + if line.start_with?(" ") + line[2..-1] + else + line + end +end + +def remove_guards(guard, keep) + Dir["*/**/*.rb"].each do |file| + contents = File.read(file) + if contents =~ guard + puts file + lines = contents.lines.to_a + while first = lines.find_index { |line| line =~ guard } + indent = lines[first][/^(\s*)/, 1].length + last = (first+1...lines.size).find { |i| + space = lines[i][/^(\s*)end$/, 1] and space.length == indent + } + raise file unless last + if keep + lines[first..last] = lines[first+1..last-1].map { |l| dedent(l) } + else + if first > 0 and lines[first-1] == "\n" + first -= 1 + elsif lines[last+1] == "\n" + last += 1 + end + lines[first..last] = [] + end + end + File.write file, lines.join + end + end +end + +version = "2.2" +remove_guards(/ruby_version_is ["']#{version}["'] do/, true) +remove_guards(/ruby_version_is ["'][0-9.]*["']...["']#{version}["'] do/, false) -- cgit v1.2.3