scarpe-components 0.1.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "minitest"
4
+ require "json"
5
+ require "json/add/exception"
6
+
7
+ module Scarpe; module Components; end; end
8
+ module Scarpe::Components::ImportRunnables
9
+ # Minitest Runnables are unusual - we expect to declare a class (like a Test) with
10
+ # a lot of methods to run. The ImportRunnable is a single Runnable. But whenever
11
+ # you tell it to import a JSON file, it will add all of the described tests to
12
+ # its runnable methods.
13
+ #
14
+ # Normally that means that your subclass tests will run up front and produce
15
+ # JSON files, then Minitest will autorun at the end and report all their
16
+ # results.
17
+ #
18
+ # It wouldn't really make sense to create these runnables during the testing
19
+ # phase, because Minitest has already decided what to run at that point.
20
+ class ImportRunnable #< Minitest::Runnable
21
+ # Import JSON from an exported Minitest run. Note that running this multiple
22
+ # times with overlapping class names may be really bad.
23
+ def self.import_json_data(data)
24
+ @imported_classes ||= {}
25
+ @imported_tests ||= {}
26
+
27
+ JSON.parse(data).each do |item|
28
+ klass = item["klass"]
29
+ meth = item["name"]
30
+ @imported_tests[klass] ||= {}
31
+ @imported_tests[klass][meth] = item
32
+ end
33
+
34
+ @imported_tests.each do |klass_name, test_method_hash|
35
+ klass = @imported_classes[klass_name]
36
+ unless klass
37
+ new_klass = Class.new(Minitest::Runnable)
38
+ @imported_classes[klass_name] = new_klass
39
+ ImportRunnable.const_set(klass_name, new_klass)
40
+ klass = new_klass
41
+
42
+ klass.define_singleton_method(:run_one_method) do |klass, method_name, reporter|
43
+ reporter.prerecord klass, method_name
44
+ imp = test_method_hash[method_name]
45
+
46
+ res = Minitest::Result.new imp["name"]
47
+ res.klass = imp["klass"]
48
+ res.assertions = imp["assertions"]
49
+ res.time = imp["time"]
50
+ res.failures = ImportRunnable.deserialize_failures imp["failures"]
51
+ res.metadata = imp["metadata"] if imp["metadata"]
52
+
53
+ # Record the synthetic result built from imported data
54
+ reporter.record res
55
+ end
56
+ end
57
+
58
+ # Update "runnables" method to reflect all current known runnable tests
59
+ klass_methods = test_method_hash.keys
60
+ klass.define_singleton_method(:runnable_methods) do
61
+ klass_methods
62
+ end
63
+ end
64
+ end
65
+
66
+ def self.json_to_err(err_json)
67
+ klass = begin
68
+ Object.const_get(err_json["json_class"])
69
+ rescue
70
+ nil
71
+ end
72
+ if klass && klass <= Minitest::Assertion
73
+ klass.json_create(err_json)
74
+ else
75
+ err = Exception.json_create(err_json)
76
+ Minitest::UnexpectedError.new(err)
77
+ end
78
+ end
79
+
80
+ def self.deserialize_failures(failures)
81
+ failures.map do |fail|
82
+ # Instantiate the Minitest::Assertion or Minitest::UnexpectedError
83
+ if fail[0] == "exception"
84
+ exc_json = JSON.parse(fail[1])
85
+ json_to_err exc_json
86
+ elsif fail[0] == "unexpected"
87
+ unexpected_json = JSON.parse(fail[1])
88
+ inner_json = JSON.parse(fail[2])
89
+ outer_err = json_to_err unexpected_json
90
+ inner_err = json_to_err inner_json
91
+ outer_err.error = inner_err
92
+ else
93
+ raise "Unknown exception data when trying to deserialize! #{fail.inspect}"
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "minitest"
4
+ require "json"
5
+ #require "json/add/exception"
6
+
7
+ module Scarpe; module Components; end; end
8
+
9
+ # A MinitestResult imports a JSON file from a minitest_export_reporter.
10
+ # But instead of creating a Minitest::Test to report the result, the
11
+ # MinitestResult is just a queryable Ruby object.
12
+ #
13
+ # MinitestResult assumes there will be only one class and one method
14
+ # in the JSON, which is true for Scarpe but not necessarily in general.
15
+ class Scarpe::Components::MinitestResult
16
+ attr_reader :assertions
17
+ attr_reader :method_name
18
+ attr_reader :class_name
19
+
20
+ def initialize(filename)
21
+ data = JSON.parse File.read(filename)
22
+
23
+ unless data.size == 1
24
+ # We would want a different interface to support this in general. For now we don't
25
+ # need it to work in general.
26
+ raise "Scarpe::Components::MinitestResult only supports one class and method in results!"
27
+ end
28
+
29
+ item = data.first
30
+
31
+ @assertions = item["assertions"]
32
+ @method_name = item["name"]
33
+ @class_name = item["klass"]
34
+ @time = item["time"]
35
+ @metadata = item.key?("metadata") ? item["metadata"]: {}
36
+
37
+ @skip = false
38
+ @exceptions = []
39
+ @failures = []
40
+ item["failures"].each do |f|
41
+ # JSON.parse ignores json_class and won't create an arbitrary object. That's good
42
+ # because Minitest::UnexpectedError seems to load in a bad way, so we don't want
43
+ # it to auto-instantiate.
44
+ d = JSON.parse f[1]
45
+ msg = d["m"]
46
+ case d["json_class"]
47
+ when "Minitest::UnexpectedError"
48
+ @exceptions << msg
49
+ when "Minitest::Skip"
50
+ @skip = msg
51
+ when "Minitest::Assertion"
52
+ @failures << msg
53
+ else
54
+ raise Scarpe::InternalError, "Didn't expect type #{t.inspect} as exception type when importing Minitest tests!"
55
+ end
56
+ end
57
+ end
58
+
59
+ def error?
60
+ !@exceptions.empty?
61
+ end
62
+
63
+ def fail?
64
+ !@failures.empty?
65
+ end
66
+
67
+ def skip?
68
+ @skip ? true : false
69
+ end
70
+
71
+ def passed?
72
+ @exceptions.empty? && @failures.empty? && !@skip
73
+ end
74
+
75
+ def error_message
76
+ @exceptions[0]
77
+ end
78
+
79
+ def fail_message
80
+ @failures[0]
81
+ end
82
+
83
+ def skip_message
84
+ @skip
85
+ end
86
+ end
@@ -5,10 +5,12 @@ require "json"
5
5
 
6
6
  require "shoes/log"
7
7
 
8
- # Requirements: logging gem
8
+ # Requires the logging gem
9
9
 
10
- class Scarpe
11
- class LogImpl
10
+ module Scarpe; end
11
+ module Scarpe::Components; end
12
+ module Scarpe
13
+ class Components::ModularLogImpl
12
14
  include Shoes::Log # for constants
13
15
 
14
16
  def logger_for_component(component)
@@ -30,7 +32,7 @@ class Scarpe
30
32
  when "fatal"
31
33
  :fatal
32
34
  else
33
- raise "Don't know how to treat #{data.inspect} as a logger severity!"
35
+ raise Shoes::Errors::InvalidAttributeValueError, "Don't know how to treat #{data.inspect} as a logger severity!"
34
36
  end
35
37
  end
36
38
 
@@ -43,7 +45,7 @@ class Scarpe
43
45
  when String
44
46
  Logging.appenders.file data, layout: @custom_log_layout
45
47
  else
46
- raise "Don't know how to convert #{data.inspect} to an appender!"
48
+ raise Shoes::Errors::InvalidAttributeValueError, "Don't know how to convert #{data.inspect} to an appender!"
47
49
  end
48
50
  end
49
51
 
@@ -62,7 +64,7 @@ class Scarpe
62
64
 
63
65
  logger.level = name_to_severity(level)
64
66
  else
65
- raise "Don't know how to use #{data.inspect} to specify a logger!"
67
+ raise Shoes::Errors::InvalidAttributeValueError, "Don't know how to use #{data.inspect} to specify a logger!"
66
68
  end
67
69
  end
68
70
 
@@ -106,3 +108,6 @@ class Scarpe
106
108
  end
107
109
  end
108
110
  end
111
+
112
+ #Shoes::Log.instance = Scarpe::Components::ModularLogImpl.new
113
+ #Shoes::Log.configure_logger(Shoes::Log::DEFAULT_LOG_CONFIG)
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "shoes/log"
4
+ require "json"
5
+
6
+ module Scarpe; end
7
+ module Scarpe::Components; end
8
+ class Scarpe::Components::PrintLogImpl
9
+ include Shoes::Log # for constants
10
+
11
+ class PrintLogger
12
+ class << self
13
+ attr_accessor :silence
14
+ end
15
+
16
+ def initialize(component_name)
17
+ @comp_name = component_name
18
+ end
19
+
20
+ def error(msg)
21
+ puts "#{@comp_name} error: #{msg}" unless PrintLogger.silence
22
+ end
23
+
24
+ def warn(msg)
25
+ puts "#{@comp_name} warn: #{msg}" unless PrintLogger.silence
26
+ end
27
+
28
+ def debug(msg)
29
+ puts "#{@comp_name} debug: #{msg}" unless PrintLogger.silence
30
+ end
31
+
32
+ def info(msg)
33
+ puts "#{@comp_name} info: #{msg}" unless PrintLogger.silence
34
+ end
35
+ end
36
+
37
+ def logger_for_component(component)
38
+ PrintLogger.new(component.to_s)
39
+ end
40
+
41
+ def configure_logger(log_config)
42
+ # For now, ignore
43
+ end
44
+ end
45
+
46
+ #Shoes::Log.instance = Scarpe::PrintLogImpl.new
47
+ #Shoes::Log.configure_logger(Shoes::Log::DEFAULT_LOG_CONFIG)