File: test_helper.rb

package info (click to toggle)
ruby-git 4.3.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 3,564 kB
  • sloc: ruby: 9,983; sh: 519; perl: 64; makefile: 6
file content (275 lines) | stat: -rw-r--r-- 9,466 bytes parent folder | download
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
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
# frozen_string_literal: true

require 'date'
require 'fileutils'
require 'minitar'
require 'test/unit'
require 'mocha/test_unit'
require 'tmpdir'

require 'git'

$stdout.sync = true
$stderr.sync = true

# Make tests that emit a deprecation warning fail
#
# Deprecation warnings should not be ignored.
#
# This is important so that:
# * when a user sees a deprecation warning, they can be confident it is coming from
#   their code and not this gem
# * test output is clean and does not contain noisey deprecation warnings
#
# Tests whose purpose is to test that a deprecation warning is issued in the right
# circumstance should mock Git::Deprecation#warn to avoid raising an error.
#
Git::Deprecation.behavior = :raise

module Test
  module Unit
    # A base class for all test cases in this project
    #
    # This class provides utility methods for setting up and tearing down test
    # environments, creating temporary repositories, and mocking the Git binary.
    #
    class TestCase
      TEST_ROOT = File.expand_path(__dir__)
      TEST_FIXTURES = File.join(TEST_ROOT, 'files')

      BARE_REPO_PATH = File.join(TEST_FIXTURES, 'working.git')

      def clone_working_repo
        @wdir = create_temp_repo('working')
      end

      teardown
      def git_teardown
        FileUtils.rm_r(@tmp_path) if instance_variable_defined?(:@tmp_path)
      end

      def in_bare_repo_clone
        in_temp_dir do |_path|
          git = Git.clone(BARE_REPO_PATH, 'bare')
          Dir.chdir('bare') do
            yield git
          end
        end
      end

      def in_temp_repo(clone_name, &)
        clone_path = create_temp_repo(clone_name)
        Dir.chdir(clone_path, &)
      end

      def create_temp_repo(clone_name)
        clone_path = File.join(TEST_FIXTURES, clone_name)
        filename = "git_test#{Time.now.to_i}#{rand(300).to_s.rjust(3, '0')}"
        path = File.expand_path(File.join(Dir.tmpdir, filename))
        FileUtils.mkdir_p(path)
        @tmp_path = File.realpath(path)
        FileUtils.cp_r(clone_path, @tmp_path)
        tmp_path = File.join(@tmp_path, File.basename(clone_path))
        FileUtils.cd tmp_path do
          FileUtils.mv('dot_git', '.git')
        end
        tmp_path
      end

      # Creates a temp directory and yields that path to the passed block
      #
      # On Windows, using Dir.mktmpdir with a block sometimes raises an error:
      # `Errno::ENOTEMPTY: Directory not empty @ dir_s_rmdir`. I think this might
      # be a configuration issue with the Windows CI environment.
      #
      # This was worked around by using the non-block form of Dir.mktmpdir and
      # then removing the directory manually in an ensure block.
      #
      def in_temp_dir
        tmpdir = Dir.mktmpdir
        tmpdir_realpath = File.realpath(tmpdir)
        Dir.chdir(tmpdir_realpath) do
          yield tmpdir_realpath
        end
      ensure
        FileUtils.rm_rf(tmpdir_realpath) if tmpdir_realpath
        # raise "Temp dir #{tmpdir} not removed. Remaining files : #{Dir["#{tmpdir}/**/*"]}" if File.exist?(tmpdir)
      end

      def create_file(path, content)
        File.open(path, 'w') do |file|
          file.puts(content)
        end
      end

      def update_file(path, content)
        create_file(path, content)
      end

      def delete_file(path)
        File.delete(path)
      end

      def move_file(source_path, target_path)
        File.rename source_path, target_path
      end

      def new_file(name, contents)
        create_file(name, contents)
      end

      def append_file(name, contents)
        File.open(name, 'a') do |f|
          f.puts contents
        end
      end

      # Assert that the expected command line is generated by a given Git::Base method
      #
      # This assertion generates an empty git repository and then yields to the
      # given block passing the Git::Base instance for the empty repository. The
      # current directory is set to the root of the repository's working tree.
      #
      #
      # @example Test that calling `git.fetch` generates the command line `git fetch`
      #   # Only need to specify the arguments to the git command
      #   expected_command_line = ['fetch']
      #   assert_command_line_eq(expected_command_line) { |git| git.fetch }
      #
      # @example Test that calling `git.fetch('origin', { ref: 'master', depth: '2' })` generates the command line `git fetch --depth 2 -- origin master`
      #   expected_command_line = ['fetch', '--depth', '2', '--', 'origin', 'master']
      #   assert_command_line_eq(expected_command_line) { |git| git.fetch('origin', { ref: 'master', depth: '2' }) }
      #
      # @param expected_command_line [Array<String>] The expected arguments to be sent to Git::Lib#command
      # @param git_output [String] The mocked output to be returned by the Git::Lib#command method
      #
      # @yield [git] a block to call the method to be tested
      # @yieldparam git [Git::Base] The Git::Base object resulting from initializing the test project
      # @yieldreturn [void] the return value of the block is ignored
      #
      # @return [void]
      #
      def assert_command_line_eq(expected_command_line, method: :command, mocked_output: '', include_env: false)
        actual_command_line = nil

        command_output = ''

        in_temp_dir do |_path|
          git = Git.init('test_project')

          git.lib.define_singleton_method(method) do |*cmd, **opts|
            actual_command_line = if include_env
                                    [env_overrides, *cmd, opts]
                                  else
                                    [*cmd, opts]
                                  end
            mocked_output
          end

          Dir.chdir 'test_project' do
            yield(git) if block_given?
          end
        end

        expected_command_line = expected_command_line.call if expected_command_line.is_a?(Proc)

        assert_equal(expected_command_line, actual_command_line)

        command_output
      end

      def assert_child_process_success
        yield
        assert_equal 0, $CHILD_STATUS.exitstatus, "Child process failed with exitstatus #{$CHILD_STATUS.exitstatus}"
      end

      def windows_platform?
        # Check if on Windows via RUBY_PLATFORM (CRuby) and RUBY_DESCRIPTION (JRuby)
        win_platform_regex = /mingw|mswin/
        RUBY_PLATFORM =~ win_platform_regex || RUBY_DESCRIPTION =~ win_platform_regex
      end

      def jruby_platform?
        RUBY_PLATFORM == 'java'
      end

      # Run a command and return the status including stdout and stderr output
      #
      # @example
      #   command = %w[git status]
      #   status = run(command)
      #   status.success? # => true
      #   status.exitstatus # => 0
      #   status.out # => "On branch master\nnothing to commit, working tree clean\n"
      #   status.err # => ""
      #
      # @param command [Array<String>] The command to run
      # @param timeout [Numeric, nil] Seconds to allow command to run before killing it or nil for no timeout
      # @param raise_errors [Boolean] Raise an exception if the command fails
      # @param error_message [String] The message to use when raising an exception
      #
      # @return [CommandResult] The result of running
      #
      def run_command(*command, raise_errors: true, error_message: "#{command[0]} failed")
        result = ProcessExecuter.run_with_capture(*command, raise_errors: false)

        raise "#{error_message}: #{result.stderr}" if raise_errors && !result.success?

        result
      end
    end
  end
end

# Replace the default git binary with the given script
#
# This method creates a temporary directory and writes the given script to a file
# named `git` in a subdirectory named `bin`. This subdirectory name can be changed by
# passing a different value for the `subdir` parameter.
#
# On non-windows platforms, make sure the script starts with a hash bang. On windows,
# make sure the script has a `.bat` extension.
#
# On non-windows platforms, the script is made executable.
#
# `Git::Base.config.binary_path` set to the path to the script.
#
# The block is called passing the path to the mocked git binary.
#
# `Git::Base.config.binary_path` is reset to its original value after the block
# returns.
#
# @example mocked_git_script = <<~GIT_SCRIPT #!/bin/sh puts 'git version 1.2.3'
#   GIT_SCRIPT
#
#   mock_git_binary(mocked_git_script) do
#     # Run Git commands here -- they will call the mocked git script
#   end
#
# @param script [String] The bash script to run instead of the real git binary
#
# @param subdir [String] The subdirectory to place the mocked git binary in
#
# @yield Call the block while the git binary is mocked
#
# @yieldparam git_binary_path [String] The path to the mocked git binary
#
# @yieldreturn [void] the return value of the block is ignored
#
# @return [void]
#
def mock_git_binary(script, subdir: 'bin')
  Dir.mktmpdir do |binary_dir|
    binary_name = windows_platform? ? 'git.bat' : 'git'
    git_binary_path = File.join(binary_dir, subdir, binary_name)
    FileUtils.mkdir_p(File.dirname(git_binary_path))
    File.write(git_binary_path, script)
    File.chmod(0o755, git_binary_path) unless windows_platform?
    saved_binary_path = Git::Base.config.binary_path
    Git::Base.config.binary_path = git_binary_path

    yield git_binary_path

    Git::Base.config.binary_path = saved_binary_path
  end
end