diff --git a/lib/pdk/tests/unit.rb b/lib/pdk/tests/unit.rb index c073b02b9..64fde75cb 100644 --- a/lib/pdk/tests/unit.rb +++ b/lib/pdk/tests/unit.rb @@ -32,23 +32,7 @@ def self.invoke(report, options = {}) result = command.execute! - # TODO: cleanup rspec and/or beaker output - # Iterate through possible JSON documents until we find one that is valid. - json_result = nil - json_result = [] if options.key?(:parallel) - - result[:stdout].scan(%r{\{(?:[^{}]|(?:\g<0>))*\}}x) do |str| - begin - if options.key?(:parallel) - json_result.push(JSON.parse(str)) - else - json_result = JSON.parse(str) - break - end - rescue JSON::ParserError - next - end - end + json_result = PDK::Util.find_valid_json_in(result[:stdout], break_on_first: !options.key?(:parallel)) raise PDK::CLI::FatalError, _('Unit test output did not contain a valid JSON result: %{output}') % { output: result[:stdout] } if json_result.nil? || json_result.empty? @@ -148,29 +132,28 @@ def self.merge_json_results(json_data, duration) # @return array of { :id, :full_description } def self.list PDK::Util::Bundler.ensure_bundle! - PDK::Util::Bundler.ensure_binstubs!('rspec-core') + PDK::Util::Bundler.ensure_binstubs!('rake') - command_argv = [File.join(PDK::Util.module_root, 'bin', 'rspec'), '--dry-run', '--format', 'json'] + command_argv = [File.join(PDK::Util.module_root, 'bin', 'rake'), 'spec_list_json'] command_argv.unshift('ruby') if Gem.win_platform? list_command = PDK::CLI::Exec::Command.new(*command_argv) list_command.context = :module output = list_command.execute! - rspec_json_output = JSON.parse(output[:stdout]) - if rspec_json_output['examples'].empty? - rspec_message = rspec_json_output['messages'][0] + rspec_json = PDK::Util.find_valid_json_in(output[:stdout]) + raise PDK::CLI::FatalError, _('Failed to find valid JSON in output from rspec: %{output}' % { output: output[:stdout] }) unless rspec_json + if rspec_json['examples'].empty? + rspec_message = rspec_json['messages'][0] return [] if rspec_message == 'No examples found.' raise PDK::CLI::FatalError, _('Unable to enumerate examples. rspec reported: %{message}' % { message: rspec_message }) else examples = [] - rspec_json_output['examples'].each do |example| + rspec_json['examples'].each do |example| examples << { id: example['id'], full_description: example['full_description'] } end examples end - rescue JSON::ParserError => e - raise PDK::CLI::FatalError, _('Failed to parse output from rspec: %{message}' % { message: e.message }) end end end diff --git a/lib/pdk/util.rb b/lib/pdk/util.rb index 419e8f0e1..ae8f64d01 100644 --- a/lib/pdk/util.rb +++ b/lib/pdk/util.rb @@ -100,5 +100,36 @@ def module_root end end module_function :module_root + + # Iterate through possible JSON documents until we find one that is valid. + # + # @param [String] text the text in which to find a JSON document + # @param [Hash] opts options + # @option opts [Boolean] :break_on_first Whether or not to break after valid JSON is found, defaults to true + # + # @return [Hash, Array, nil] subset of text as Hash of first valid JSON found, array of all valid JSON found, or nil if no valid + # JSON found in the text + def find_valid_json_in(text, opts = {}) + break_on_first = opts.key?(:break_on_first) ? opts[:break_on_first] : true + + json_result = break_on_first ? nil : [] + + text.scan(%r{\{(?:[^{}]|(?:\g<0>))*\}}x) do |str| + begin + if break_on_first + json_result = JSON.parse(str) + break + else + json_result.push(JSON.parse(str)) + end + rescue JSON::ParserError + next + end + end + + return nil if json_result.nil? || json_result.empty? + json_result + end + module_function :find_valid_json_in end end diff --git a/spec/acceptance/test_unit_spec.rb b/spec/acceptance/test_unit_spec.rb index 8d33547f3..42efcf916 100644 --- a/spec/acceptance/test_unit_spec.rb +++ b/spec/acceptance/test_unit_spec.rb @@ -52,6 +52,11 @@ its(:exit_status) { is_expected.to eq(0) } its(:stderr) { is_expected.to match(%r{running unit tests.*3 tests.*0 failures}im) } end + + describe command('pdk test unit --parallel') do + its(:exit_status) { is_expected.to eq(0) } + its(:stderr) { is_expected.to match(%r{running unit tests in parallel.*3 tests.*0 failures}im) } + end end context 'with failing tests' do @@ -138,4 +143,53 @@ its(:stderr) { is_expected.to match(%r{SyntaxError}) } end end + + context 'multiple files with passing tests' do + include_context 'in a new module', 'unit_test_module_multiple_pass' + + before(:all) do + FileUtils.mkdir_p('spec/unit') + # FIXME: facterversion pin and facterdb issues + File.open('spec/unit/passing_one_spec.rb', 'w') do |f| + f.puts <<-EOF + require 'spec_helper' + + RSpec.describe 'passing test' do + on_supported_os(:facterversion => '2.4.6').each do |os, facts| + context "On OS \#{os}" do + it 'should pass' do + expect(true).to eq(true) + end + end + end + end + EOF + end + File.open('spec/unit/passing_two_spec.rb', 'w') do |f| + f.puts <<-EOF + require 'spec_helper' + + RSpec.describe 'passing test' do + on_supported_os(:facterversion => '2.4.6').each do |os, facts| + context "On OS \#{os}" do + it 'should pass' do + expect(true).to eq(true) + end + end + end + end + EOF + end + end + + describe command('pdk test unit') do + its(:exit_status) { is_expected.to eq(0) } + its(:stderr) { is_expected.to match(%r{running unit tests.*6 tests.*0 failures}im) } + end + + describe command('pdk test unit --parallel') do + its(:exit_status) { is_expected.to eq(0) } + its(:stderr) { is_expected.to match(%r{running unit tests in parallel.*6 tests.*0 failures}im) } + end + end end diff --git a/spec/unit/pdk/util_spec.rb b/spec/unit/pdk/util_spec.rb index e52528e5b..710b974d5 100644 --- a/spec/unit/pdk/util_spec.rb +++ b/spec/unit/pdk/util_spec.rb @@ -239,4 +239,58 @@ it { is_expected.to be_nil } end end + + # TODO: These expect a string rather than actual JSON. Primarily they expect the non-JSON string to be removed + describe '.find_valid_json_in' do + it 'returns JSON from start of a string' do + text = '{"version":"3.6.0", "examples":[]}bar' + expected = { 'version' => '3.6.0', 'examples' => [] } + + expect(described_class.find_valid_json_in(text)).to eq(expected) + end + + it 'returns JSON from the end of a string' do + text = 'foo{"version":"3.6.0", "examples":[]}' + expected = { 'version' => '3.6.0', 'examples' => [] } + + expect(described_class.find_valid_json_in(text)).to eq(expected) + end + + it 'returns JSON from the middle of a string' do + text = 'foo{"version":"3.6.0", "examples":[]}bar' + expected = { 'version' => '3.6.0', 'examples' => [] } + + expect(described_class.find_valid_json_in(text)).to eq(expected) + end + + it 'returns nil for invalid JSON in a string' do + text = 'foo{"version"":"3.6.0", "examples":[]}bar' + + expect(described_class.find_valid_json_in(text)).to be_nil + end + + it 'returns nil for no JSON in a string' do + text = 'foosomethingbar' + + expect(described_class.find_valid_json_in(text)).to be_nil + end + + it 'returns first JSON document from string with multiple valid docs' do + text = '{"version": "3.6.0", "examples": []}{"version": "4.6.0", "examples": []}' + expected = { 'version' => '3.6.0', 'examples' => [] } + + expect(described_class.find_valid_json_in(text)).to eq(expected) + end + + context 'when break_on_first option is set to false' do + let(:opts) { { break_on_first: false } } + + it 'returns array of JSON documents from string with multiple valid docs' do + text = '{"version": "3.6.0", "examples": []}{"version": "4.6.0", "examples": []}' + expected = [{ 'version' => '3.6.0', 'examples' => [] }, { 'version' => '4.6.0', 'examples' => [] }] + + expect(described_class.find_valid_json_in(text, opts)).to contain_exactly(*expected) + end + end + end end