rspec-junit-formatter-0.4.1/0000755000175000017500000000000013714003130014774 5ustar pravipravirspec-junit-formatter-0.4.1/lib/0000755000175000017500000000000013714003130015542 5ustar pravipravirspec-junit-formatter-0.4.1/lib/rspec_junit_formatter/0000755000175000017500000000000013714003130022152 5ustar pravipravirspec-junit-formatter-0.4.1/lib/rspec_junit_formatter/rspec3.rb0000644000175000017500000000772613714003130023712 0ustar pravipraviclass RSpecJUnitFormatter < RSpec::Core::Formatters::BaseFormatter RSpec::Core::Formatters.register self, :start, :stop, :dump_summary def start(notification) @start_notification = notification @started = Time.now super end def stop(notification) @examples_notification = notification end def dump_summary(notification) @summary_notification = notification without_color { xml_dump } end private attr_reader :started def example_count @summary_notification.example_count end def pending_count @summary_notification.pending_count end def failure_count @summary_notification.failure_count end def duration @summary_notification.duration end def examples @examples_notification.notifications end def result_of(notification) notification.example.execution_result.status end def example_group_file_path_for(notification) metadata = notification.example.metadata[:example_group] while parent_metadata = metadata[:parent_example_group] metadata = parent_metadata end metadata[:file_path] end def classname_for(notification) fp = example_group_file_path_for(notification) fp.sub(%r{\.[^/]*\Z}, "").gsub("/", ".").gsub(%r{\A\.+|\.+\Z}, "") end def duration_for(notification) notification.example.execution_result.run_time end def description_for(notification) notification.example.full_description end def failure_type_for(example) exception_for(example).class.name end def failure_message_for(example) strip_diff_colors(exception_for(example).to_s) end def failure_for(notification) strip_diff_colors(notification.message_lines.join("\n")) << "\n" << notification.formatted_backtrace.join("\n") end def exception_for(notification) notification.example.execution_result.exception end # rspec makes it really difficult to swap in configuration temporarily due to # the way it cascades defaults, command line arguments, and user # configuration. This method makes sure configuration gets swapped in # correctly, but also that the original state is definitely restored. def swap_rspec_configuration(key, value) unset = Object.new force = RSpec.configuration.send(:value_for, key) { unset } if unset.equal?(force) previous = RSpec.configuration.send(key) RSpec.configuration.send(:"#{key}=", value) else RSpec.configuration.force({key => value}) end yield ensure if unset.equal?(force) RSpec.configuration.send(:"#{key}=", previous) else RSpec.configuration.force({key => force}) end end # Completely gross hack for absolutely forcing off colorising for the # duration of a block. if RSpec.configuration.respond_to?(:color_mode=) def without_color(&block) swap_rspec_configuration(:color_mode, :off, &block) end elsif RSpec.configuration.respond_to?(:color=) def without_color(&block) swap_rspec_configuration(:color, false, &block) end else warn 'rspec_junit_formatter cannot prevent colorising due to an unexpected RSpec.configuration format' def without_color yield end end def stdout_for(example_notification) example_notification.example.metadata[:stdout] end def stderr_for(example_notification) example_notification.example.metadata[:stderr] end end # rspec-core 3.0.x forgot to mark this as a module function which causes: # # NoMethodError: undefined method `wrap' for RSpec::Core::Notifications::NullColorizer:Class # .../rspec-core-3.0.4/lib/rspec/core/notifications.rb:229:in `add_shared_group_line' # .../rspec-core-3.0.4/lib/rspec/core/notifications.rb:157:in `message_lines' # if defined?(RSpec::Core::Notifications::NullColorizer) && RSpec::Core::Notifications::NullColorizer.is_a?(Class) && !RSpec::Core::Notifications::NullColorizer.respond_to?(:wrap) RSpec::Core::Notifications::NullColorizer.class_eval do def self.wrap(*args) new.wrap(*args) end end end rspec-junit-formatter-0.4.1/lib/rspec_junit_formatter/rspec2.rb0000644000175000017500000000352613714003130023703 0ustar pravipraviclass RSpecJUnitFormatter < RSpec::Core::Formatters::BaseFormatter attr_reader :started def start(example_count) @started = Time.now super end def dump_summary(duration, example_count, failure_count, pending_count) super xml_dump end private def result_of(example) example.execution_result[:status].to_sym end def example_group_file_path_for(example) meta = example.metadata while meta[:example_group] meta = meta[:example_group] end meta[:file_path] end def classname_for(example) fp = example_group_file_path_for(example) fp.sub(%r{\.[^/.]+\Z}, "").gsub("/", ".").gsub(/\A\.+|\.+\Z/, "") end def duration_for(example) example.execution_result[:run_time] end def description_for(example) example.full_description end def exception_for(example) example.execution_result[:exception] end def failure_type_for(example) exception_for(example).class.name end def failure_message_for(example) strip_diff_colors(exception_for(example).to_s) end def failure_for(example) exception = exception_for(example) message = strip_diff_colors(exception.message) backtrace = format_backtrace(exception.backtrace, example) if shared_group = find_shared_group(example) backtrace << "Shared Example Group: \"#{shared_group.metadata[:shared_group_name]}\" called from #{shared_group.metadata[:example_group][:location]}" end "#{message}\n#{backtrace.join("\n")}" end def find_shared_group(example) group_and_parent_groups(example).find { |group| group.metadata[:shared_group_name] } end def group_and_parent_groups(example) example.example_group.parent_groups + [example.example_group] end def stdout_for(example) example.metadata[:stdout] end def stderr_for(example) example.metadata[:stderr] end end rspec-junit-formatter-0.4.1/lib/rspec_junit_formatter.rb0000644000175000017500000001341113714003130022477 0ustar pravipravirequire "socket" require "time" require "rspec/core" require "rspec/core/formatters/base_formatter" # Dumps rspec results as a JUnit XML file. # Based on XML schema: http://windyroad.org/dl/Open%20Source/JUnit.xsd class RSpecJUnitFormatter < RSpec::Core::Formatters::BaseFormatter # rspec 2 and 3 implements are in separate files. private def xml_dump output << %{\n} output << %{\n} output << %{\n} output << %{\n} output << %{\n} xml_dump_examples output << %{\n} end def xml_dump_examples examples.each do |example| case result_of(example) when :pending xml_dump_pending(example) when :failed xml_dump_failed(example) else xml_dump_example(example) end end end def xml_dump_pending(example) xml_dump_example(example) do output << %{} end end def xml_dump_failed(example) xml_dump_example(example) do output << %{} output << escape(failure_for(example)) output << %{} end end def xml_dump_example(example) output << %{} yield if block_given? xml_dump_output(example) output << %{\n} end def xml_dump_output(example) if (stdout = stdout_for(example)) && !stdout.empty? output << %{} output << escape(stdout) output << %{} end if (stderr = stderr_for(example)) && !stderr.empty? output << %{} output << escape(stderr) output << %{} end end # Inversion of character range from https://www.w3.org/TR/xml/#charsets ILLEGAL_REGEXP = Regexp.new( "[^" << "\u{9}" << # => \t "\u{a}" << # => \n "\u{d}" << # => \r "\u{20}-\u{d7ff}" << "\u{e000}-\u{fffd}" << "\u{10000}-\u{10ffff}" << "]" ) # Replace illegals with a Ruby-like escape ILLEGAL_REPLACEMENT = Hash.new { |_, c| x = c.ord if x <= 0xff "\\x%02X".freeze % x elsif x <= 0xffff "\\u%04X".freeze % x else "\\u{%X}".freeze % x end.freeze }.update( "\0".freeze => "\\0".freeze, "\a".freeze => "\\a".freeze, "\b".freeze => "\\b".freeze, "\f".freeze => "\\f".freeze, "\v".freeze => "\\v".freeze, "\e".freeze => "\\e".freeze, ).freeze # Discouraged characters from https://www.w3.org/TR/xml/#charsets # Plus special characters with well-known entity replacements DISCOURAGED_REGEXP = Regexp.new( "[" << "\u{22}" << # => " "\u{26}" << # => & "\u{27}" << # => ' "\u{3c}" << # => < "\u{3e}" << # => > "\u{7f}-\u{84}" << "\u{86}-\u{9f}" << "\u{fdd0}-\u{fdef}" << "\u{1fffe}-\u{1ffff}" << "\u{2fffe}-\u{2ffff}" << "\u{3fffe}-\u{3ffff}" << "\u{4fffe}-\u{4ffff}" << "\u{5fffe}-\u{5ffff}" << "\u{6fffe}-\u{6ffff}" << "\u{7fffe}-\u{7ffff}" << "\u{8fffe}-\u{8ffff}" << "\u{9fffe}-\u{9ffff}" << "\u{afffe}-\u{affff}" << "\u{bfffe}-\u{bffff}" << "\u{cfffe}-\u{cffff}" << "\u{dfffe}-\u{dffff}" << "\u{efffe}-\u{effff}" << "\u{ffffe}-\u{fffff}" << "\u{10fffe}-\u{10ffff}" << "]" ) # Translate well-known entities, or use generic unicode hex entity DISCOURAGED_REPLACEMENTS = Hash.new { |_, c| "&#x#{c.ord.to_s(16)};".freeze }.update( ?".freeze => """.freeze, ?&.freeze => "&".freeze, ?'.freeze => "'".freeze, ?<.freeze => "<".freeze, ?>.freeze => ">".freeze, ).freeze def escape(text) # Make sure it's utf-8, replace illegal characters with ruby-like escapes, and replace special and discouraged characters with entities text.to_s.encode(Encoding::UTF_8).gsub(ILLEGAL_REGEXP, ILLEGAL_REPLACEMENT).gsub(DISCOURAGED_REGEXP, DISCOURAGED_REPLACEMENTS) end STRIP_DIFF_COLORS_BLOCK_REGEXP = /^ ( [ ]* ) Diff: (?: \e\[ 0 m )? (?: \n \1 \e\[ \d+ (?: ; \d+ )* m .* )* /x STRIP_DIFF_COLORS_CODES_REGEXP = /\e\[ \d+ (?: ; \d+ )* m/x def strip_diff_colors(string) # XXX: RSpec diffs are appended to the message lines fairly early and will # contain ANSI escape codes for colorizing terminal output if the global # rspec configuration is turned on, regardless of which notification lines # we ask for. We need to strip the codes from the diff part of the message # for XML output here. # # We also only want to target the diff hunks because the failure message # itself might legitimately contain ansi escape codes. # string.sub(STRIP_DIFF_COLORS_BLOCK_REGEXP) { |match| match.gsub(STRIP_DIFF_COLORS_CODES_REGEXP, "".freeze) } end end RspecJunitFormatter = RSpecJUnitFormatter if Gem::Version.new(RSpec::Core::Version::STRING) >= Gem::Version.new("3") require "rspec_junit_formatter/rspec3" else require "rspec_junit_formatter/rspec2" end rspec-junit-formatter-0.4.1/checksums.yaml.gz.sig0000444000175000017500000000040013714003130021035 0ustar pravipravi(3E2M;8K`8"}Z /3ftKPa :n&Sc9Z;:ozЛ|W4+"ޟ]EIi\U3S41%nRn>t{z94=QDXNM+C$IJ/?= y.`Et*I< 9kG_Jq=z#v)=R6ۺZ\OKeh.y ņ[`*S4g\:rspec-junit-formatter-0.4.1/data.tar.gz.sig0000444000175000017500000000040013714003130017605 0ustar pravipraviR%3i]^./J3hzJ# pF5 61D(f4o )j=P0(J;e'k̰cԒR޾n3 iP2+Œnk]zwM*{BBqu94e]ăf6ۣJ2_^|-AI,[jAqoŬk 0KC'@Xb*jCRVo340r"c"KӅTF6Y>k01|Brspec-junit-formatter-0.4.1/README.md0000644000175000017500000000757313714003130016267 0ustar pravipravi# RSpec JUnit Formatter [![Build results](http://img.shields.io/travis/sj26/rspec_junit_formatter/master.svg)](https://travis-ci.org/sj26/rspec_junit_formatter) [![Gem version](http://img.shields.io/gem/v/rspec_junit_formatter.svg)](https://rubygems.org/gems/rspec_junit_formatter) [RSpec][rspec] 2 & 3 results that your CI can read. [Jenkins][jenkins-junit], [Buildkite][buildkite-junit], [CircleCI][circleci-junit], and probably more, too. [rspec]: http://rspec.info/ [jenkins-junit]: https://jenkins.io/doc/pipeline/steps/junit/ [buildkite-junit]: https://github.com/buildkite/rspec-junit-example [circleci-junit]: https://circleci.com/docs/2.0/collect-test-data/ ## Usage Install the gem: ```sh gem install rspec_junit_formatter ``` Use it: ```sh rspec --format RspecJunitFormatter --out rspec.xml ``` You'll get an XML file `rspec.xml` with your results in it. You can use it in combination with other [formatters][rspec-formatters], too: ```sh rspec --format progress --format RspecJunitFormatter --out rspec.xml ``` [rspec-formatters]: https://relishapp.com/rspec/rspec-core/v/3-6/docs/formatters ### Using in your project with Bundler Add it to your Gemfile if you're using [Bundler][bundler]. Put it in the same groups as rspec. ```ruby group :test do gem "rspec" gem "rspec_junit_formatter" end ``` Put the same arguments as the commands above in [your `.rspec`][rspec-file]: ```sh --format RspecJunitFormatter --out rspec.xml ``` [bundler]: https://bundler.io [rspec-file]: https://relishapp.com/rspec/rspec-core/v/3-6/docs/configuration/read-command-line-configuration-options-from-files ### Parallel tests For use with `parallel_tests`, add `$TEST_ENV_NUMBER` in the output file option (in `.rspec` or `.rspec_parallel`) to avoid concurrent process write conflicts. ```sh --format RspecJunitFormatter --out tmp/rspec<%= ENV["TEST_ENV_NUMBER"] %>.xml ``` The formatter includes `$TEST_ENV_NUMBER` in the test suite name within the XML, too. ### Capturing output If you like, you can capture the standard output and error streams of each test into the `:stdout` and `:stderr` example metadata which will be added to the junit report, e.g.: ```ruby # spec_helper.rb RSpec.configure do |config| # register around filter that captures stdout and stderr config.around(:each) do |example| $stdout = StringIO.new $stderr = StringIO.new example.run example.metadata[:stdout] = $stdout.string example.metadata[:stderr] = $stderr.string $stdout = STDOUT $stderr = STDERR end end ``` ## Caveats * XML can only represent a [limited subset of characters][xml-charsets] which excludes null bytes and most control characters. This gem will use character entities where possible and fall back to replacing invalid characters with Ruby-like escape codes otherwise. For example, the null byte becomes `\0`. [xml-charsets]: https://www.w3.org/TR/xml/#charsets ## Roadmap * It would be nice to split things up into individual test suites, although would this correspond to example groups? The subject? The spec file? Not sure yet. ## Development Run the specs with `bundle exec rake`, which uses [Appraisal][appraisal] to run the specs against all supported versions of rspec. [appraisal]: https://github.com/thoughtbot/appraisal ## Releasing Bump the gem version in the gemspec, and commit. Then `bundle exec rake build` to build a gem package, `bundle exec rake install` to install and test it locally, then `bundle exec rake release` to tag and push the commits and gem. ## License The MIT License, see [LICENSE](./LICENSE). ## Thanks Inspired by the work of [Diego Souza][dgvncsz0f] on [RSpec Formatters][dgvncsz0f/rspec_formatters] after frustration with [CI Reporter][ci_reporter]. [dgvncsz0f]: https://github.com/dgvncsz0f [dgvncsz0f/rspec_formatters]: https://github.com/dgvncsz0f/rspec_formatters [ci_reporter]: https://github.com/nicksieger/ci_reporter rspec-junit-formatter-0.4.1/metadata.gz.sig0000444000175000017500000000040013714003130017667 0ustar pravipravil@$lYo ^+0 2&^w^bV1aɺq[ T a޻C(y540`o1+ -+([ā# 0ԜGنqHKQ ؼ'rNYAfwOC'^w F%FV'5q:~V/ϥ|,d;e)}W{)px5e9 5o\Е̝Ro&` őrspec-junit-formatter-0.4.1/rspec_junit_formatter.gemspec0000644000175000017500000000645013714003130022756 0ustar pravipravi######################################################### # This file has been automatically generated by gem2tgz # ######################################################### # -*- encoding: utf-8 -*- # stub: rspec_junit_formatter 0.4.1 ruby lib Gem::Specification.new do |s| s.name = "rspec_junit_formatter".freeze s.version = "0.4.1" s.required_rubygems_version = Gem::Requirement.new(">= 2.0.0".freeze) if s.respond_to? :required_rubygems_version= s.metadata = { "changelog_uri" => "https://github.com/sj26/rspec_junit_formatter/blob/master/CHANGELOG.md" } if s.respond_to? :metadata= s.require_paths = ["lib".freeze] s.authors = ["Samuel Cochran".freeze] s.cert_chain = ["-----BEGIN CERTIFICATE-----\nMIIDKDCCAhCgAwIBAgIBBTANBgkqhkiG9w0BAQUFADA6MQ0wCwYDVQQDDARzajI2\nMRQwEgYKCZImiZPyLGQBGRYEc2oyNjETMBEGCgmSJomT8ixkARkWA2NvbTAeFw0x\nNzA3MzEwNTQ3MDVaFw0xODA3MzEwNTQ3MDVaMDoxDTALBgNVBAMMBHNqMjYxFDAS\nBgoJkiaJk/IsZAEZFgRzajI2MRMwEQYKCZImiZPyLGQBGRYDY29tMIIBIjANBgkq\nhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsr60Eo/ttCk8GMTMFiPr3GoYMIMFvLak\nxSmTk9YGCB6UiEePB4THSSA5w6IPyeaCF/nWkDp3/BAam0eZMWG1IzYQB23TqIM0\n1xzcNRvFsn0aQoQ00k+sj+G83j3T5OOV5OZIlu8xAChMkQmiPd1NXc6uFv+Iacz7\nkj+CMsI9YUFdNoU09QY0b+u+Rb6wDYdpyvN60YC30h0h1MeYbvYZJx/iZK4XY5zu\n4O/FL2ChjL2CPCpLZW55ShYyrzphWJwLOJe+FJ/ZBl6YXwrzQM9HKnt4titSNvyU\nKzE3L63A3PZvExzLrN9u09kuWLLJfXB2sGOlw3n9t72rJiuBr3/OQQIDAQABozkw\nNzAJBgNVHRMEAjAAMAsGA1UdDwQEAwIEsDAdBgNVHQ4EFgQU99dfRjEKFyczTeIz\nm3ZsDWrNC80wDQYJKoZIhvcNAQEFBQADggEBADGiXpvK754s0zTFx3y31ZRDdvAI\nlA209JIjUlDyr9ptCRadihyfF2l9/hb+hLemiPEYppzG6vEK1TIyzbAR36yOJ8CX\n4vPkCXLuwHhs6UIRbwN+IEy41nsIlBxmjLYei8h3t/G2Vm2oOaLdbjDXS+Srl9U8\nshsE8ft81PxSQfzEL7Mr9cC9XvWbHW+SyTpfGm8rAtaqZkNeke4U8a0di4oz2EfA\nP4lSfmXxsd1C71ckIp0cyXkPhyTtpyS/5hq9HhuUNzEHkSDe36/Rd1xYKV5JxMC2\nYAttWFUs06lor2q1wwncPaMtUtbWwW35+1IV6xhs2rFY6DD/I6ZkK3GnHdY=\n-----END CERTIFICATE-----\n".freeze] s.date = "2018-05-30" s.description = "RSpec results that your continuous integration service can read.".freeze s.email = "sj26@sj26.com".freeze s.files = ["LICENSE".freeze, "README.md".freeze, "lib/rspec_junit_formatter.rb".freeze, "lib/rspec_junit_formatter/rspec2.rb".freeze, "lib/rspec_junit_formatter/rspec3.rb".freeze] s.homepage = "http://github.com/sj26/rspec_junit_formatter".freeze s.licenses = ["MIT".freeze] s.required_ruby_version = Gem::Requirement.new(">= 2.0.0".freeze) s.rubygems_version = "3.1.2".freeze s.summary = "RSpec JUnit XML formatter".freeze if s.respond_to? :specification_version then s.specification_version = 4 end if s.respond_to? :add_runtime_dependency then s.add_development_dependency(%q.freeze, [">= 0"]) s.add_development_dependency(%q.freeze, [">= 0"]) s.add_development_dependency(%q.freeze, [">= 0"]) s.add_development_dependency(%q.freeze, ["~> 1.8", ">= 1.8.2"]) s.add_development_dependency(%q.freeze, [">= 0"]) s.add_runtime_dependency(%q.freeze, [">= 2", "< 4", "!= 2.12.0"]) else s.add_dependency(%q.freeze, [">= 0"]) s.add_dependency(%q.freeze, [">= 0"]) s.add_dependency(%q.freeze, [">= 0"]) s.add_dependency(%q.freeze, ["~> 1.8", ">= 1.8.2"]) s.add_dependency(%q.freeze, [">= 0"]) s.add_dependency(%q.freeze, [">= 2", "< 4", "!= 2.12.0"]) end end rspec-junit-formatter-0.4.1/LICENSE0000644000175000017500000000204713714003130016004 0ustar pravipraviCopyright (c) 2011-2018 Samuel Cochran 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.