ro 4.4.0 → 5.0.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/Gemfile.lock +42 -16
- data/MIGRATION.md +320 -0
- data/README.md +30 -18
- data/a.yml +60 -0
- data/bin/ro +10 -0
- data/lib/ro/_lib.rb +1 -1
- data/lib/ro/asset.rb +46 -5
- data/lib/ro/collection.rb +51 -13
- data/lib/ro/migrator.rb +285 -0
- data/lib/ro/node.rb +53 -13
- data/lib/ro/root.rb +75 -1
- data/lib/ro/script/migrate.rb +204 -0
- data/lib/ro/script/server.rb +1 -1
- data/lib/ro.rb +1 -0
- data/ro.gemspec +207 -16
- data/specs/001-simplify-asset-structure/IMPLEMENTATION_SUMMARY.md +212 -0
- data/specs/001-simplify-asset-structure/checklists/requirements.md +36 -0
- data/specs/001-simplify-asset-structure/contracts/collection_api.md +407 -0
- data/specs/001-simplify-asset-structure/contracts/migrator_api.md +461 -0
- data/specs/001-simplify-asset-structure/contracts/node_api.md +294 -0
- data/specs/001-simplify-asset-structure/data-model.md +381 -0
- data/specs/001-simplify-asset-structure/plan.md +90 -0
- data/specs/001-simplify-asset-structure/quickstart.md +575 -0
- data/specs/001-simplify-asset-structure/research.md +333 -0
- data/specs/001-simplify-asset-structure/spec.md +127 -0
- data/specs/001-simplify-asset-structure/tasks.md +349 -0
- data/test/fixtures/new_structure/mixed/test-json.json +5 -0
- data/test/fixtures/new_structure/mixed/test-yaml.yml +3 -0
- data/test/fixtures/new_structure/posts/metadata-only.yml +7 -0
- data/test/fixtures/new_structure/posts/nested-test/assets/subdirectory/image.png +2 -0
- data/test/fixtures/new_structure/posts/nested-test.yml +7 -0
- data/test/fixtures/new_structure/posts/sample-post/assets/body.md +5 -0
- data/test/fixtures/new_structure/posts/sample-post/assets/image.jpg +2 -0
- data/test/fixtures/new_structure/posts/sample-post.yml +7 -0
- data/test/fixtures/old_structure/posts/assets-only/assets/test.txt +1 -0
- data/test/fixtures/old_structure/posts/sample-post/assets/body.md +5 -0
- data/test/fixtures/old_structure/posts/sample-post/assets/image.jpg +2 -0
- data/test/fixtures/old_structure/posts/sample-post/attributes.yml +2 -0
- data/test/integration/ro_integration_test.rb +165 -0
- data/test/test_helper.rb +149 -0
- data/test/tmp/migration_test_1760746513.backup.20251018001513/migration_test_1760746513/posts/sample-post/assets/image.jpg +2 -0
- data/test/tmp/migration_test_1760746513.backup.20251018001513/migration_test_1760746513/posts/sample-post/attributes.yml +7 -0
- data/test/tmp/migration_test_1760746513.backup.20251018001513/migration_test_1760746513/posts/sample-post/body.md +5 -0
- data/test/tmp/migration_test_1760746513.backup.20251018001513/posts/sample-post/assets/image.jpg +2 -0
- data/test/tmp/migration_test_1760746513.backup.20251018001513/posts/sample-post/attributes.yml +7 -0
- data/test/tmp/migration_test_1760746513.backup.20251018001513/posts/sample-post/body.md +5 -0
- data/test/tmp/migration_test_1760746556.backup.20251018001556/migration_test_1760746556/posts/sample-post/assets/image.jpg +2 -0
- data/test/tmp/migration_test_1760746556.backup.20251018001556/migration_test_1760746556/posts/sample-post/attributes.yml +7 -0
- data/test/tmp/migration_test_1760746556.backup.20251018001556/migration_test_1760746556/posts/sample-post/body.md +5 -0
- data/test/tmp/migration_test_1760746556.backup.20251018001556/posts/sample-post/assets/image.jpg +2 -0
- data/test/tmp/migration_test_1760746556.backup.20251018001556/posts/sample-post/attributes.yml +7 -0
- data/test/tmp/migration_test_1760746556.backup.20251018001556/posts/sample-post/body.md +5 -0
- data/test/tmp/migration_test_1760755248.backup.20251018024048/migration_test_1760755248/posts/sample-post/assets/image.jpg +2 -0
- data/test/tmp/migration_test_1760755248.backup.20251018024048/migration_test_1760755248/posts/sample-post/attributes.yml +7 -0
- data/test/tmp/migration_test_1760755248.backup.20251018024048/migration_test_1760755248/posts/sample-post/body.md +5 -0
- data/test/tmp/migration_test_1760755248.backup.20251018024048/posts/sample-post/assets/image.jpg +2 -0
- data/test/tmp/migration_test_1760755248.backup.20251018024048/posts/sample-post/attributes.yml +7 -0
- data/test/tmp/migration_test_1760755248.backup.20251018024048/posts/sample-post/body.md +5 -0
- data/test/tmp/migration_test_1760758803.backup.20251018034003/migration_test_1760758803/posts/sample-post/body.md +5 -0
- data/test/tmp/migration_test_1760758803.backup.20251018034003/migration_test_1760758803/posts/sample-post/image.jpg +2 -0
- data/test/tmp/migration_test_1760758803.backup.20251018034003/migration_test_1760758803/posts/sample-post.yml +7 -0
- data/test/tmp/migration_test_1760758803.backup.20251018034003/posts/sample-post/body.md +5 -0
- data/test/tmp/migration_test_1760758803.backup.20251018034003/posts/sample-post/image.jpg +2 -0
- data/test/tmp/migration_test_1760758803.backup.20251018034003/posts/sample-post.yml +7 -0
- data/test/tmp/migration_test_1760758869.backup.20251018034109/migration_test_1760758869/posts/sample-post/assets/body.md +5 -0
- data/test/tmp/migration_test_1760758869.backup.20251018034109/migration_test_1760758869/posts/sample-post/assets/image.jpg +2 -0
- data/test/tmp/migration_test_1760758869.backup.20251018034109/migration_test_1760758869/posts/sample-post/attributes.yml +2 -0
- data/test/tmp/migration_test_1760758869.backup.20251018034109/posts/sample-post/assets/body.md +5 -0
- data/test/tmp/migration_test_1760758869.backup.20251018034109/posts/sample-post/assets/image.jpg +2 -0
- data/test/tmp/migration_test_1760758869.backup.20251018034109/posts/sample-post/attributes.yml +2 -0
- data/test/tmp/migration_test_1760758920.backup.20251018034200/migration_test_1760758920/posts/sample-post/assets/body.md +5 -0
- data/test/tmp/migration_test_1760758920.backup.20251018034200/migration_test_1760758920/posts/sample-post/assets/image.jpg +2 -0
- data/test/tmp/migration_test_1760758920.backup.20251018034200/migration_test_1760758920/posts/sample-post/attributes.yml +2 -0
- data/test/tmp/migration_test_1760758920.backup.20251018034200/posts/sample-post/assets/body.md +5 -0
- data/test/tmp/migration_test_1760758920.backup.20251018034200/posts/sample-post/assets/image.jpg +2 -0
- data/test/tmp/migration_test_1760758920.backup.20251018034200/posts/sample-post/attributes.yml +2 -0
- data/test/tmp/migration_test_1760824728.backup.20251018215848/migration_test_1760824728/posts/assets-only/assets/test.txt +1 -0
- data/test/tmp/migration_test_1760824728.backup.20251018215848/migration_test_1760824728/posts/sample-post/assets/body.md +5 -0
- data/test/tmp/migration_test_1760824728.backup.20251018215848/migration_test_1760824728/posts/sample-post/assets/image.jpg +2 -0
- data/test/tmp/migration_test_1760824728.backup.20251018215848/migration_test_1760824728/posts/sample-post/attributes.yml +2 -0
- data/test/tmp/migration_test_1760824728.backup.20251018215848/posts/assets-only/assets/test.txt +1 -0
- data/test/tmp/migration_test_1760824728.backup.20251018215848/posts/sample-post/assets/body.md +5 -0
- data/test/tmp/migration_test_1760824728.backup.20251018215848/posts/sample-post/assets/image.jpg +2 -0
- data/test/tmp/migration_test_1760824728.backup.20251018215848/posts/sample-post/attributes.yml +2 -0
- data/test/tmp/migration_test_1760844153.backup.20251019032233/migration_test_1760844153/posts/assets-only/assets/test.txt +1 -0
- data/test/tmp/migration_test_1760844153.backup.20251019032233/migration_test_1760844153/posts/sample-post/assets/body.md +5 -0
- data/test/tmp/migration_test_1760844153.backup.20251019032233/migration_test_1760844153/posts/sample-post/assets/image.jpg +2 -0
- data/test/tmp/migration_test_1760844153.backup.20251019032233/migration_test_1760844153/posts/sample-post/attributes.yml +2 -0
- data/test/tmp/migration_test_1760844153.backup.20251019032233/posts/assets-only/assets/test.txt +1 -0
- data/test/tmp/migration_test_1760844153.backup.20251019032233/posts/sample-post/assets/body.md +5 -0
- data/test/tmp/migration_test_1760844153.backup.20251019032233/posts/sample-post/assets/image.jpg +2 -0
- data/test/tmp/migration_test_1760844153.backup.20251019032233/posts/sample-post/attributes.yml +2 -0
- data/test/tmp/new_structure_test_1760746452/mixed/test-json.json +5 -0
- data/test/tmp/new_structure_test_1760746452/mixed/test-yaml.yml +3 -0
- data/test/tmp/new_structure_test_1760746452/posts/metadata-only.yml +7 -0
- data/test/tmp/new_structure_test_1760746452/posts/nested-test/subdirectory/image.png +2 -0
- data/test/tmp/new_structure_test_1760746452/posts/nested-test.yml +7 -0
- data/test/tmp/new_structure_test_1760746452/posts/sample-post/body.md +5 -0
- data/test/tmp/new_structure_test_1760746452/posts/sample-post/image.jpg +2 -0
- data/test/tmp/new_structure_test_1760746452/posts/sample-post.yml +7 -0
- data/test/unit/asset_test.rb +90 -0
- data/test/unit/collection_test.rb +127 -0
- data/test/unit/migrator_test.rb +209 -0
- data/test/unit/node_test.rb +138 -0
- metadata +111 -18
- /data/public/ro/nerd/{fastest-possible-embeddings/attributes.yml → fastest-possible-embeddings.yml} +0 -0
- /data/public/ro/nerd/{ima/attributes.yml → ima.yml} +0 -0
- /data/public/ro/nerd/{index/attributes.yml → index.yml} +0 -0
- /data/public/ro/pages/{contact/attributes.yml → contact.yml} +0 -0
- /data/public/ro/pages/{cv/attributes.yml → cv.yml} +0 -0
- /data/public/ro/pages/{disco/attributes.yml → disco.yml} +0 -0
- /data/public/ro/pages/{index/attributes.yml → index.yml} +0 -0
- /data/public/ro/pages/{jess/attributes.yml → jess.yml} +0 -0
- /data/public/ro/pages/{now/attributes.yml → now.yml} +0 -0
- /data/public/ro/posts/{almost-died-in-an-ice-cave/attributes.yml → almost-died-in-an-ice-cave.yml} +0 -0
- /data/public/ro/posts/{facebook-and-global-extremism/attributes.yml → facebook-and-global-extremism.yml} +0 -0
- /data/public/ro/posts/{lemmings-considered-harmful/attributes.yml → lemmings-considered-harmful.yml} +0 -0
- /data/public/ro/posts/{lost-in-the-desert/attributes.yml → lost-in-the-desert.yml} +0 -0
- /data/public/ro/posts/{mission/attributes.yml → mission.yml} +0 -0
- /data/public/ro/posts/{return-your-laptop/attributes.yml → return-your-laptop.yml} +0 -0
data/test/test_helper.rb
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# Test Helper for ro gem v5.0 tests
|
|
2
|
+
#
|
|
3
|
+
# Common setup, assertions, and utilities for unit and integration tests
|
|
4
|
+
|
|
5
|
+
require 'pathname'
|
|
6
|
+
require 'fileutils'
|
|
7
|
+
require 'yaml'
|
|
8
|
+
require 'json'
|
|
9
|
+
|
|
10
|
+
# Load the ro gem
|
|
11
|
+
$LOAD_PATH.unshift File.expand_path('../lib', __dir__)
|
|
12
|
+
require 'ro'
|
|
13
|
+
|
|
14
|
+
module TestHelper
|
|
15
|
+
# Get path to test fixtures directory
|
|
16
|
+
def fixtures_path
|
|
17
|
+
Pathname.new(File.expand_path('../fixtures', __FILE__))
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Get path to old structure fixtures
|
|
21
|
+
def old_structure_path
|
|
22
|
+
fixtures_path / 'old_structure'
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Get path to new structure fixtures
|
|
26
|
+
def new_structure_path
|
|
27
|
+
fixtures_path / 'new_structure'
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Create a temporary test directory
|
|
31
|
+
def create_temp_dir(name = 'test')
|
|
32
|
+
dir = Pathname.new(File.expand_path("../tmp/#{name}_#{Time.now.to_i}", __FILE__))
|
|
33
|
+
FileUtils.mkdir_p(dir)
|
|
34
|
+
dir
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Clean up temporary directory
|
|
38
|
+
def cleanup_temp_dir(dir)
|
|
39
|
+
FileUtils.rm_rf(dir) if dir && dir.exist?
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Assert that a path exists
|
|
43
|
+
def assert_path_exists(path, message = nil)
|
|
44
|
+
assert Pathname.new(path).exist?, message || "Expected path to exist: #{path}"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Assert that a file contains specific content
|
|
48
|
+
def assert_file_contains(path, content, message = nil)
|
|
49
|
+
actual = File.read(path)
|
|
50
|
+
assert actual.include?(content), message || "Expected file #{path} to contain: #{content}"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Assert that a YAML file has a specific key/value
|
|
54
|
+
def assert_yaml_has(path, key, value = nil, message = nil)
|
|
55
|
+
data = YAML.load_file(path)
|
|
56
|
+
assert data.key?(key.to_s) || data.key?(key.to_sym), message || "Expected YAML to have key: #{key}"
|
|
57
|
+
if value
|
|
58
|
+
actual_value = data[key.to_s] || data[key.to_sym]
|
|
59
|
+
assert_equal value, actual_value, message || "Expected #{key} to equal #{value}, got #{actual_value}"
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Create a test node in old structure format
|
|
64
|
+
def create_old_structure_node(collection_path, node_id, attributes = {}, assets = [])
|
|
65
|
+
node_dir = collection_path / node_id
|
|
66
|
+
FileUtils.mkdir_p(node_dir)
|
|
67
|
+
FileUtils.mkdir_p(node_dir / 'assets')
|
|
68
|
+
|
|
69
|
+
# Write attributes.yml
|
|
70
|
+
File.write(node_dir / 'attributes.yml', attributes.to_yaml)
|
|
71
|
+
|
|
72
|
+
# Create asset files
|
|
73
|
+
assets.each do |asset_file|
|
|
74
|
+
asset_path = node_dir / 'assets' / asset_file
|
|
75
|
+
FileUtils.mkdir_p(asset_path.dirname)
|
|
76
|
+
File.write(asset_path, "test content for #{asset_file}")
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
node_dir
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Create a test node in new structure format
|
|
83
|
+
def create_new_structure_node(collection_path, node_id, attributes = {}, assets = [])
|
|
84
|
+
FileUtils.mkdir_p(collection_path)
|
|
85
|
+
|
|
86
|
+
# Write metadata file (identifier.yml)
|
|
87
|
+
File.write(collection_path / "#{node_id}.yml", attributes.to_yaml)
|
|
88
|
+
|
|
89
|
+
# Create asset directory and files
|
|
90
|
+
if assets.any?
|
|
91
|
+
node_dir = collection_path / node_id
|
|
92
|
+
FileUtils.mkdir_p(node_dir)
|
|
93
|
+
|
|
94
|
+
assets.each do |asset_file|
|
|
95
|
+
asset_path = node_dir / asset_file
|
|
96
|
+
FileUtils.mkdir_p(asset_path.dirname)
|
|
97
|
+
File.write(asset_path, "test content for #{asset_file}")
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
collection_path / "#{node_id}.yml"
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Base test class
|
|
106
|
+
class RoTestCase
|
|
107
|
+
include TestHelper
|
|
108
|
+
|
|
109
|
+
def setup
|
|
110
|
+
# Override in subclasses
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def teardown
|
|
114
|
+
# Override in subclasses
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Simple assertion methods (compatible with custom test runner)
|
|
118
|
+
def assert(condition, message = "Assertion failed")
|
|
119
|
+
raise AssertionError, message unless condition
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def assert_equal(expected, actual, message = nil)
|
|
123
|
+
msg = message || "Expected #{expected.inspect}, got #{actual.inspect}"
|
|
124
|
+
assert expected == actual, msg
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def assert_nil(actual, message = nil)
|
|
128
|
+
msg = message || "Expected nil, got #{actual.inspect}"
|
|
129
|
+
assert actual.nil?, msg
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def assert_not_nil(actual, message = nil)
|
|
133
|
+
msg = message || "Expected non-nil value"
|
|
134
|
+
assert !actual.nil?, msg
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def assert_raises(exception_class, message = nil)
|
|
138
|
+
begin
|
|
139
|
+
yield
|
|
140
|
+
assert false, message || "Expected #{exception_class} to be raised"
|
|
141
|
+
rescue exception_class
|
|
142
|
+
# Expected
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
class AssertionError < StandardError; end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
puts "Test helper loaded successfully"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
test asset
|
data/test/tmp/migration_test_1760824728.backup.20251018215848/posts/assets-only/assets/test.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
test asset
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
test asset
|
data/test/tmp/migration_test_1760844153.backup.20251019032233/posts/assets-only/assets/test.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
test asset
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# Unit tests for Ro::Asset with new structure support
|
|
3
|
+
|
|
4
|
+
require_relative '../test_helper'
|
|
5
|
+
|
|
6
|
+
class AssetTest < RoTestCase
|
|
7
|
+
def setup
|
|
8
|
+
@root = Ro::Root.new(new_structure_path)
|
|
9
|
+
@collection = Ro::Collection.new(new_structure_path.join('posts'))
|
|
10
|
+
@metadata_file = new_structure_path.join('posts', 'sample-post.yml')
|
|
11
|
+
@node = Ro::Node.new(@collection, @metadata_file)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# T017: Test Asset path resolution with assets/ subdirectory
|
|
15
|
+
def test_asset_path_with_assets_subdirectory
|
|
16
|
+
asset_path = new_structure_path / 'posts' / 'sample-post' / 'assets' / 'image.jpg'
|
|
17
|
+
|
|
18
|
+
# Both old and new structure use assets/ subdirectory
|
|
19
|
+
assert asset_path.to_s.include?('/assets/'), "Asset path should contain /assets/ subdirectory"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def test_asset_relative_path_calculation
|
|
23
|
+
asset_path = new_structure_path / 'posts' / 'sample-post' / 'assets' / 'image.jpg'
|
|
24
|
+
asset = Ro::Asset.new(asset_path)
|
|
25
|
+
|
|
26
|
+
# Relative path should be calculated correctly (relative to asset_dir which is node/assets/)
|
|
27
|
+
relative = asset.relative_path
|
|
28
|
+
|
|
29
|
+
assert_not_nil relative, "Relative path should not be nil"
|
|
30
|
+
# Relative path is relative to asset_dir (node/assets/), so just the filename
|
|
31
|
+
assert_equal 'image.jpg', relative.to_s, "Relative path should be image.jpg"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def test_nested_asset_path
|
|
35
|
+
# Test with nested subdirectory structure
|
|
36
|
+
asset_path = new_structure_path / 'posts' / 'nested-test' / 'assets' / 'subdirectory' / 'image.png'
|
|
37
|
+
asset = Ro::Asset.new(asset_path)
|
|
38
|
+
|
|
39
|
+
relative = asset.relative_path
|
|
40
|
+
|
|
41
|
+
# Should preserve subdirectory structure (relative to assets/)
|
|
42
|
+
assert_equal 'subdirectory/image.png', relative.to_s, "Nested asset should preserve directory structure"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def test_asset_url_generation
|
|
46
|
+
asset_path = new_structure_path / 'posts' / 'sample-post' / 'image.jpg'
|
|
47
|
+
asset = Ro::Asset.new(asset_path)
|
|
48
|
+
|
|
49
|
+
# Asset should be able to generate URL
|
|
50
|
+
# (Exact URL format depends on node.url_for implementation)
|
|
51
|
+
assert_not_nil asset, "Asset should be created successfully"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def test_asset_belongs_to_node
|
|
55
|
+
asset_paths = @node.asset_paths
|
|
56
|
+
|
|
57
|
+
assert asset_paths.any?, "Node should have asset paths"
|
|
58
|
+
|
|
59
|
+
# All asset paths should be within node directory
|
|
60
|
+
asset_paths.each do |path|
|
|
61
|
+
assert path.to_s.start_with?(@node.asset_dir.to_s), "Asset path should be within node directory"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Run the tests
|
|
67
|
+
if __FILE__ == $0
|
|
68
|
+
test = AssetTest.new
|
|
69
|
+
|
|
70
|
+
puts "Running Asset unit tests..."
|
|
71
|
+
|
|
72
|
+
tests = [
|
|
73
|
+
:test_asset_path_with_assets_subdirectory,
|
|
74
|
+
:test_asset_relative_path_calculation,
|
|
75
|
+
:test_nested_asset_path,
|
|
76
|
+
:test_asset_url_generation,
|
|
77
|
+
:test_asset_belongs_to_node
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
tests.each do |test_method|
|
|
81
|
+
begin
|
|
82
|
+
test.setup
|
|
83
|
+
test.send(test_method)
|
|
84
|
+
puts "✓ #{test_method}"
|
|
85
|
+
rescue => e
|
|
86
|
+
puts "✗ #{test_method}: #{e.message}"
|
|
87
|
+
puts " #{e.backtrace.first}"
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|