scarpe-components 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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)