|
6 | 6 | # Usage: |
7 | 7 | # bin/validate-patterns # validate every patterns file |
8 | 8 | # bin/validate-patterns path/to/file # validate a specific file (one or more) |
| 9 | +# bin/validate-patterns --self-test # run built-in fixture assertions |
9 | 10 | # |
10 | | -# Exits 0 if all files pass, 1 otherwise. |
| 11 | +# Exits 0 if all files / assertions pass, 1 otherwise. |
11 | 12 |
|
12 | 13 | require "yaml" |
| 14 | +require "tmpdir" |
13 | 15 |
|
14 | 16 | PATTERNS_DIR = File.expand_path("../rails-upgrade/detection-scripts/patterns", __dir__) |
15 | 17 | TOP_LEVEL_KEYS = %w[version description upgrade_findings].freeze |
@@ -72,6 +74,90 @@ def validate(path) |
72 | 74 | errors |
73 | 75 | end |
74 | 76 |
|
| 77 | +def self_test |
| 78 | + base = { |
| 79 | + "version" => "test", |
| 80 | + "description" => "self-test fixture", |
| 81 | + "upgrade_findings" => { |
| 82 | + "high_priority" => [ |
| 83 | + { |
| 84 | + "name" => "Test entry", |
| 85 | + "kind" => "breaking", |
| 86 | + "pattern" => "foo", |
| 87 | + "exclude" => "", |
| 88 | + "search_paths" => ["app/"], |
| 89 | + "explanation" => "test", |
| 90 | + "fix" => "test", |
| 91 | + "variable_name" => "TEST" |
| 92 | + } |
| 93 | + ] |
| 94 | + } |
| 95 | + } |
| 96 | + |
| 97 | + cases = [] |
| 98 | + |
| 99 | + KIND_VALUES.each do |kind| |
| 100 | + doc = Marshal.load(Marshal.dump(base)) |
| 101 | + doc["upgrade_findings"]["high_priority"][0]["kind"] = kind |
| 102 | + cases << { name: "valid kind=#{kind}", doc: doc, expect: :pass } |
| 103 | + end |
| 104 | + |
| 105 | + missing_top = Marshal.load(Marshal.dump(base)) |
| 106 | + missing_top.delete("upgrade_findings") |
| 107 | + cases << { name: "missing upgrade_findings", doc: missing_top, expect: :fail, match: /missing top-level key: upgrade_findings/ } |
| 108 | + |
| 109 | + missing_kind = Marshal.load(Marshal.dump(base)) |
| 110 | + missing_kind["upgrade_findings"]["high_priority"][0].delete("kind") |
| 111 | + cases << { name: "missing pattern key kind", doc: missing_kind, expect: :fail, match: /missing key: kind/ } |
| 112 | + |
| 113 | + bad_regex = Marshal.load(Marshal.dump(base)) |
| 114 | + bad_regex["upgrade_findings"]["high_priority"][0]["pattern"] = "[unclosed" |
| 115 | + cases << { name: "broken regex", doc: bad_regex, expect: :fail, match: /pattern regex does not compile/ } |
| 116 | + |
| 117 | + bad_kind = Marshal.load(Marshal.dump(base)) |
| 118 | + bad_kind["upgrade_findings"]["high_priority"][0]["kind"] = "bogus" |
| 119 | + cases << { name: "unknown kind value", doc: bad_kind, expect: :fail, match: /invalid kind: "bogus"/ } |
| 120 | + |
| 121 | + passed = 0 |
| 122 | + failed = [] |
| 123 | + |
| 124 | + Dir.mktmpdir do |dir| |
| 125 | + cases.each_with_index do |c, i| |
| 126 | + path = File.join(dir, "fixture_#{i}.yml") |
| 127 | + File.write(path, c[:doc].to_yaml) |
| 128 | + errors = validate(path) |
| 129 | + |
| 130 | + ok = |
| 131 | + case c[:expect] |
| 132 | + when :pass then errors.empty? |
| 133 | + when :fail then errors.any? { |e| e =~ c[:match] } |
| 134 | + end |
| 135 | + |
| 136 | + if ok |
| 137 | + passed += 1 |
| 138 | + else |
| 139 | + failed << { name: c[:name], expected: c[:expect], errors: errors } |
| 140 | + end |
| 141 | + end |
| 142 | + end |
| 143 | + |
| 144 | + total = cases.size |
| 145 | + if failed.empty? |
| 146 | + puts "OK self-test (#{passed}/#{total} passed)" |
| 147 | + 0 |
| 148 | + else |
| 149 | + puts "FAIL self-test (#{passed}/#{total} passed)" |
| 150 | + failed.each do |f| |
| 151 | + puts " - #{f[:name]} (expected #{f[:expected]}, got errors: #{f[:errors].inspect})" |
| 152 | + end |
| 153 | + 1 |
| 154 | + end |
| 155 | +end |
| 156 | + |
| 157 | +if ARGV.include?("--self-test") |
| 158 | + exit self_test |
| 159 | +end |
| 160 | + |
75 | 161 | paths = |
76 | 162 | if ARGV.empty? |
77 | 163 | Dir.glob(File.join(PATTERNS_DIR, "*.yml")).sort |
|
0 commit comments