omoteura-core 0.1.1 → 0.2.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.
- checksums.yaml +4 -4
- data/lib/omoteura/core/version.rb +1 -1
- data/lib/omoteura/coverage_report.rb +14 -8
- data/lib/omoteura/results_file.rb +64 -0
- data/lib/omoteura/source_index.rb +50 -2
- data/lib/omoteura/test_results.rb +5 -3
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e172672c45474fc44869d70f9e0812ff21f47462d83cd93c9961e30c18cee160
|
|
4
|
+
data.tar.gz: 41798125dd13656c98913fa30eb2749ead7a2789db8cdd37b17c062cc0126587
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2b42bd503d9a835faf6570bb70880e384ab7b9321a99df4429cf77286ddf8c980e4645cfaf502e1762f09db7ffe18f4d934a42cd102eed9d33c64baa534fe91d
|
|
7
|
+
data.tar.gz: e00f1732c0972ce1aff13cb897500656b7ff3c6401f2e5449e98e8095ad79ad52ddd52e0b4c5500ce4647bc1e22c1dd585f4167fcac65371beead57868bae4ea
|
|
@@ -5,6 +5,7 @@ module Omoteura
|
|
|
5
5
|
# for the editor's coverage panel. Separate from diagrams: it works on any
|
|
6
6
|
# file and needs none.
|
|
7
7
|
#
|
|
8
|
+
# The examples are RSpec's or Minitest's, from the file's spec or test.
|
|
8
9
|
# An item counts as covered by an example that names it, in the example's
|
|
9
10
|
# description or its code: "validates :email" by an example mentioning
|
|
10
11
|
# email, "def display_name" by one inside describe "#display_name". The
|
|
@@ -40,16 +41,19 @@ module Omoteura
|
|
|
40
41
|
Example = Data.define(:path, :line, :label, :description, :source, :status)
|
|
41
42
|
private_constant :ONE_LINER, :Example
|
|
42
43
|
|
|
43
|
-
# The
|
|
44
|
-
#
|
|
45
|
-
#
|
|
44
|
+
# The tests for a file by RSpec's and Rails' conventions:
|
|
45
|
+
# app/models/user.rb is tested in spec/models/user_spec.rb or
|
|
46
|
+
# test/models/user_test.rb, lib/a/b.rb in spec/a/b_spec.rb,
|
|
47
|
+
# spec/lib/a/b_spec.rb, test/a/b_test.rb or test/lib/a/b_test.rb. A
|
|
48
|
+
# project with both kinds gets both.
|
|
46
49
|
def spec_paths(code_path)
|
|
47
50
|
base = code_path.delete_suffix(File.extname(code_path))
|
|
48
|
-
|
|
49
|
-
if base.start_with?("app/") then [
|
|
50
|
-
elsif base.start_with?("lib/") then [
|
|
51
|
-
else [
|
|
51
|
+
names =
|
|
52
|
+
if base.start_with?("app/") then [base.delete_prefix("app/")]
|
|
53
|
+
elsif base.start_with?("lib/") then [base.delete_prefix("lib/"), base]
|
|
54
|
+
else [base]
|
|
52
55
|
end
|
|
56
|
+
candidates = names.map { |name| "spec/#{name}_spec.rb" } + names.map { |name| "test/#{name}_test.rb" }
|
|
53
57
|
candidates.select { |path| (full = @config.expand(path)) && File.file?(full) }
|
|
54
58
|
end
|
|
55
59
|
|
|
@@ -66,7 +70,9 @@ module Omoteura
|
|
|
66
70
|
(described + undescribed).sort_by(&:start_line).map do |entry|
|
|
67
71
|
result = recorded[entry.start_line]
|
|
68
72
|
static = (entry.kind == :spec) ? entry.name : nil
|
|
69
|
-
|
|
73
|
+
# A Minitest test method, such as test_total, names what it tests
|
|
74
|
+
# between underscores.
|
|
75
|
+
description = [static, static&.tr("_", " "), result&.full_description].compact.uniq.join("\n")
|
|
70
76
|
Example.new(path: spec_path, line: entry.start_line, label: static || result&.full_description || entry.name, description: description,
|
|
71
77
|
source: lines[(entry.start_line - 1)...entry.end_line].join, status: result&.status)
|
|
72
78
|
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "coverage"
|
|
4
|
+
require "json"
|
|
5
|
+
require "time"
|
|
6
|
+
|
|
7
|
+
module Omoteura
|
|
8
|
+
# Builds the test results file that the RSpec formatter and the Minitest
|
|
9
|
+
# reporter write and TestResults reads. Loaded on its own by those, so it
|
|
10
|
+
# requires nothing else of Omoteura.
|
|
11
|
+
module ResultsFile
|
|
12
|
+
FORMAT_VERSION = 1
|
|
13
|
+
DEFAULT_PATH = "tmp/omoteura/results.json"
|
|
14
|
+
ANSI_ESCAPE = /\e\[[0-9;]*m/
|
|
15
|
+
# Code that is not the project's own, or is the tests themselves.
|
|
16
|
+
NOT_PROJECT_CODE = %r{\A(?:spec|test|vendor|tmp|node_modules)/}
|
|
17
|
+
|
|
18
|
+
module_function
|
|
19
|
+
|
|
20
|
+
# examples are Hashes with :id, :file_path, :line_number,
|
|
21
|
+
# :full_description, :status ("passed", "failed" or "pending") and
|
|
22
|
+
# :message.
|
|
23
|
+
def generate(examples)
|
|
24
|
+
counts = examples.map { |e| e[:status] }.tally
|
|
25
|
+
data = {
|
|
26
|
+
version: FORMAT_VERSION,
|
|
27
|
+
generated_at: Time.now.utc.iso8601,
|
|
28
|
+
summary: {examples: examples.size, failures: counts.fetch("failed", 0), pending: counts.fetch("pending", 0)},
|
|
29
|
+
examples: examples
|
|
30
|
+
}
|
|
31
|
+
executed = executed_lines
|
|
32
|
+
data[:executed_lines] = executed if executed
|
|
33
|
+
JSON.pretty_generate(data) + "\n"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# A message as a terminal would show it, without the colors.
|
|
37
|
+
def plain(text)
|
|
38
|
+
text&.gsub(ANSI_ESCAPE, "")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Line numbers that ran, by file relative to the working directory, or
|
|
42
|
+
# nil when coverage is not being measured. Something else, such as
|
|
43
|
+
# SimpleCov, must have started it: a coverage tool starting after the
|
|
44
|
+
# test framework's integration would fail.
|
|
45
|
+
def executed_lines
|
|
46
|
+
return nil unless ::Coverage.running?
|
|
47
|
+
|
|
48
|
+
root = File.join(Dir.pwd, "")
|
|
49
|
+
::Coverage.peek_result.each_with_object({}) do |(path, counts), executed|
|
|
50
|
+
next unless path.start_with?(root) && path.end_with?(".rb")
|
|
51
|
+
|
|
52
|
+
relative = path.delete_prefix(root)
|
|
53
|
+
next if relative.match?(NOT_PROJECT_CODE)
|
|
54
|
+
|
|
55
|
+
# Hash for Coverage.start(lines: true), as SimpleCov uses; an
|
|
56
|
+
# Array of line counts for the older Coverage.start.
|
|
57
|
+
lines = counts.is_a?(Hash) ? counts[:lines] : counts
|
|
58
|
+
next unless lines
|
|
59
|
+
|
|
60
|
+
executed[relative] = lines.each_index.select { |i| lines[i]&.positive? }.map { |i| i + 1 }
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -36,7 +36,9 @@ module Omoteura
|
|
|
36
36
|
EXAMPLE_GROUPS = %i[describe context feature example_group xdescribe xcontext xfeature fdescribe fcontext ffeature].freeze
|
|
37
37
|
EXAMPLES = %i[it specify example scenario xit xspecify xexample xscenario fit fspecify fexample fscenario].freeze
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
# test_class is true for a Minitest test class, where test "…" blocks
|
|
40
|
+
# are examples.
|
|
41
|
+
Group = Data.define(:description, :module, :test_class)
|
|
40
42
|
|
|
41
43
|
attr_reader :entries
|
|
42
44
|
|
|
@@ -57,10 +59,29 @@ module Omoteura
|
|
|
57
59
|
def visit_def_node(node)
|
|
58
60
|
name = node.receiver.is_a?(Prism::SelfNode) ? "self.#{node.name}" : node.name.name
|
|
59
61
|
add(:method, name, node)
|
|
62
|
+
# def test_… in a test class is a test as much as test "…" is. A link
|
|
63
|
+
# by its name finds the example first, at the same line.
|
|
64
|
+
if node.receiver.nil? && @groups.last&.test_class && name.start_with?("test_")
|
|
65
|
+
description = full_description(name)
|
|
66
|
+
add(:spec, description, node) if description
|
|
67
|
+
end
|
|
68
|
+
super
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# A Minitest test class is an example group described by its name, as
|
|
72
|
+
# "UserTest", so that its tests are examples like RSpec's.
|
|
73
|
+
def visit_class_node(node)
|
|
74
|
+
return super unless test_class?(node)
|
|
75
|
+
|
|
76
|
+
description = full_description(node.constant_path.slice)
|
|
77
|
+
add(:spec, description, node, group: true) if description
|
|
78
|
+
@groups.push(Group.new(description: description, module: true, test_class: true))
|
|
60
79
|
super
|
|
80
|
+
@groups.pop
|
|
61
81
|
end
|
|
62
82
|
|
|
63
83
|
def visit_call_node(node)
|
|
84
|
+
return visit_minitest_test(node) if minitest_test?(node)
|
|
64
85
|
return super unless spec_call?(node)
|
|
65
86
|
|
|
66
87
|
part, is_module = description_of(node)
|
|
@@ -68,7 +89,7 @@ module Omoteura
|
|
|
68
89
|
add(:spec, description, node, group: EXAMPLE_GROUPS.include?(node.name)) if description
|
|
69
90
|
|
|
70
91
|
if EXAMPLE_GROUPS.include?(node.name)
|
|
71
|
-
@groups.push(Group.new(description: description, module: is_module))
|
|
92
|
+
@groups.push(Group.new(description: description, module: is_module, test_class: false))
|
|
72
93
|
super
|
|
73
94
|
@groups.pop
|
|
74
95
|
else
|
|
@@ -78,6 +99,33 @@ module Omoteura
|
|
|
78
99
|
|
|
79
100
|
private
|
|
80
101
|
|
|
102
|
+
# test "requires an email" do … end, as ActiveSupport::TestCase defines
|
|
103
|
+
# tests, directly in a test class.
|
|
104
|
+
def minitest_test?(node)
|
|
105
|
+
node.name == :test && node.receiver.nil? && node.block && @groups.last&.test_class &&
|
|
106
|
+
node.arguments&.arguments&.first.is_a?(Prism::StringNode) || false
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def visit_minitest_test(node)
|
|
110
|
+
description = full_description(node.arguments.arguments.first.unescaped)
|
|
111
|
+
add(:spec, description, node) if description
|
|
112
|
+
# Groups opened inside the test are not examples of the class.
|
|
113
|
+
@groups.push(Group.new(description: nil, module: false, test_class: false))
|
|
114
|
+
visit_child_nodes(node)
|
|
115
|
+
@groups.pop
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# A class named like UserTest, or one that inherits from a class named
|
|
119
|
+
# like ActiveSupport::TestCase or Minitest::Test. Symbols are compared
|
|
120
|
+
# so that ordinary classes cost no allocation.
|
|
121
|
+
def test_class?(node)
|
|
122
|
+
return true if node.name.end_with?("Test")
|
|
123
|
+
|
|
124
|
+
superclass = node.superclass
|
|
125
|
+
(superclass.is_a?(Prism::ConstantReadNode) || superclass.is_a?(Prism::ConstantPathNode)) &&
|
|
126
|
+
superclass.name.end_with?("Test", "TestCase")
|
|
127
|
+
end
|
|
128
|
+
|
|
81
129
|
# Cuts the statement's first line out of the source by byte offsets.
|
|
82
130
|
# Slicing the whole statement would copy most of the file for every
|
|
83
131
|
# class or describe block, and again for each level of nesting.
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "json"
|
|
4
|
+
require "omoteura/results_file"
|
|
4
5
|
|
|
5
6
|
module Omoteura
|
|
6
|
-
# Test results written by Omoteura::RSpec::Formatter
|
|
7
|
+
# Test results written by Omoteura::RSpec::Formatter or the Minitest
|
|
8
|
+
# reporter; see ResultsFile.
|
|
7
9
|
class TestResults
|
|
8
10
|
class Invalid < Error; end
|
|
9
11
|
|
|
10
|
-
DEFAULT_PATH =
|
|
11
|
-
SUPPORTED_VERSION =
|
|
12
|
+
DEFAULT_PATH = ResultsFile::DEFAULT_PATH
|
|
13
|
+
SUPPORTED_VERSION = ResultsFile::FORMAT_VERSION
|
|
12
14
|
|
|
13
15
|
Example = Data.define(:file_path, :line_number, :full_description, :status, :message)
|
|
14
16
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: omoteura-core
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- KAIBYAKU K.K
|
|
@@ -35,6 +35,7 @@ files:
|
|
|
35
35
|
- lib/omoteura/outline.rb
|
|
36
36
|
- lib/omoteura/project.rb
|
|
37
37
|
- lib/omoteura/resolver.rb
|
|
38
|
+
- lib/omoteura/results_file.rb
|
|
38
39
|
- lib/omoteura/source_index.rb
|
|
39
40
|
- lib/omoteura/test_results.rb
|
|
40
41
|
- lib/omoteura/writer.rb
|