File: testrbl.rb

package info (click to toggle)
ruby-maxitest 6.2.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 468 kB
  • sloc: ruby: 1,587; makefile: 7
file content (194 lines) | stat: -rw-r--r-- 6,427 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# BEGIN generated by rake update, do not modify
# https://raw.githubusercontent.com/grosser/testrbl/master/lib/testrbl.rb
module Maxitest
  module Testrbl
    PATTERNS = [
      /^(\s+)(should|test|it)(\s+|\s*\(\s*)['"](.*)['"](\s*\))?\s+do\s*(?:#.*)?$/,
      /^(\s+)(context|describe)(\s+|\s*\(\s*)['"]?(.*?)['"]?(\s*\))?\s+do\s*(?:#.*)?$/,
      /^(\s+)def(\s+)(test_)([a-z_\d]+)\s*(?:#.*)?$/
    ]

    OPTION_WITH_ARGUMENT = ["-I", "-r", "-n", "--name", "-e", "--exclude", "-s", "--seed"]
    INTERPOLATION = /\\\#\\\{.*?\\\}/

    class << self
      def run_from_cli(argv)
        files, options = partition_argv(argv)
        files.concat(changed_files) if options.delete("--changed")
        files = ["test"] if files.empty?
        files = files.map { |f| localize(f) }
        load_options, options = partition_options(options)

        if files.size == 1 and files.first =~ /^(\S+):(\d+)$/
          file = $1
          line = $2
          run(ruby + load_options + line_pattern_option(file, line) + options)
        else
          if files.size == 1 and File.file?(files.first)
            run(ruby + load_options + files + options)
          elsif options.none? { |arg| arg =~ /^-n/ }
            seed = if seed = options.index("--seed")
              ["--"] + options.slice!(seed, 2)
            else
              []
            end
            files = files.map { |f| File.directory?(f) ? all_test_files_in(f) : f }.flatten
            run(ruby + load_options + files.map { |f| "-r#{f}" } + options + ["-e", ""] + seed)
          else # pass though
            # no bundle exec: projects with mini and unit-test do not run well via bundle exec testrb
            run ["testrb"] + argv
          end
        end
      end

      # overwritten by maxitest to just return line
      def line_pattern_option(file, line)
        [file, "-n", "/#{pattern_from_file(File.readlines(file), line)}/"]
      end

      # usable via external tools like zeus
      def pattern_from_file(lines, line)
        possible_lines = lines[0..(line.to_i-1)].reverse

        found = possible_lines.map { |line| test_pattern_from_line(line) || block_start_from_line(line) }.compact

        # pattern and the groups it is nested under (like describe - describe - it)
        last_spaces = " " * 100
        patterns = found.select do |spaces, name|
          last_spaces = spaces if spaces.size < last_spaces.size
        end.map(&:last).compact

        return filter_duplicate_final(patterns).reverse.join(".*") if found.size > 0

        raise "no test found before line #{line}"
      end

      # only keep 1 pattern that stops matching via $
      def filter_duplicate_final(patterns)
        found_final = 0
        patterns.reject { |p| p.end_with?("$") and (found_final += 1) > 1 }
      end

      private

      def all_test_files_in(folder)
        Dir[File.join(folder, "{**/,}*_{test,spec}.rb")].uniq
      end

      def partition_options(options)
        next_is_before = false
        options.partition do |option|
          if next_is_before
            next_is_before = false
            true
          else
            if option =~ /^-(r|I)/
              next_is_before = (option.size == 2)
              true
            else
              false
            end
          end
        end
      end

      # fix 1.9 not being able to load local files
      def localize(file)
        file =~ /^[-a-z\d_]/ ? "./#{file}" : file
      end

      def partition_argv(argv)
        next_is_option = false
        argv.partition do |arg|
          if next_is_option
            next_is_option = false
          else
            if arg =~ /^-.$/ or  arg =~ /^--/ # single letter option followed by argument like -I test or long options like --verbose
              next_is_option = true if OPTION_WITH_ARGUMENT.include?(arg)
              false
            elsif arg =~ /^-/ # multi letter option like -Itest
              false
            else
              true
            end
          end
        end
      end

      def changed_files
        changed_files = sh("git status -s").split("\n").map { |l| l.strip.split(/\s+/, 2)[1] }

        if changed_files.empty?
          # user wants to test last commit and not current diff
          changed_files = sh("git show --name-only").split("\n\n").last.split("\n")
        end

        # we only want test files that were added or changed (not deleted)
        changed_files.select { |f| f =~ /_(test|spec)\.rb$/ && File.exist?(f) }
      end

      def sh(command)
        result = `#{command}`
        raise "Failed: #{command} -> #{result}" unless $?.success?
        result
      end

      def ruby
        if File.file?("Gemfile")
          ["ruby", "-rbundler/setup"] # faster then bundle exec ruby
        else
          ["ruby"]
        end
      end

      def run(command)
        puts command.join(" ")
        STDOUT.flush # if exec fails horribly we at least see some output
        Kernel.exec *command
      end

      def block_start_from_line(line)
        if line =~ /^(\s*).* do( \|.*\|)?$/
          [$1, nil]
        end
      end

      def test_pattern_from_line(line)
        PATTERNS.each do |r|
          next unless line =~ r
          whitespace, method, test_name = $1, $2, $4
          return [whitespace, test_pattern_from_match(method, test_name)]
        end
        nil
      end

      def test_pattern_from_match(method, test_name)
        regex = Regexp.escape(test_name).gsub("\\ "," ").gsub(INTERPOLATION, ".*")

        regex = if method == "test"
          # test "xxx -_ yyy"
          # test-unit:     "test: xxx -_ yyy"
          # activesupport: "test_xxx_-__yyy"
          "^test(: |_)#{regex.gsub(" ", ".")}$"
        elsif method == "describe" || (method == "context" && !via_shoulda?)
          "#{regex}(::)?"
        elsif method == "should" && via_shoulda?
          optional_test_name = "(?:\(.*\))?"
          "#{method} #{regex}\. #{optional_test_name}$"
        elsif ["it", "should"].include?(method) # minitest aliases for shoulda
          "#test_\\d+_#{regex}$"
        else
          regex
        end

        regex.gsub("'", ".")
      end

      def via_shoulda?
        return @via_shoulda if defined?(@via_shoulda)
        @via_shoulda = !File.exist?("Gemfile.lock") || File.read("Gemfile.lock").include?(" shoulda-context ")
      end
    end
  end
end
# END generated by rake update, do not modify