Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

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

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

327

328

329

330

331

332

333

334

335

336

337

338

339

340

341

342

343

344

345

346

347

348

349

350

351

352

353

354

355

356

357

358

359

360

361

362

363

364

365

366

367

368

369

370

371

372

373

374

375

376

377

378

379

380

381

382

383

384

385

386

387

388

389

390

391

392

393

394

395

396

397

398

399

400

401

402

403

404

405

406

407

408

409

410

411

412

413

414

415

416

417

418

419

420

421

422

423

424

425

426

427

428

429

430

431

432

433

434

435

436

437

438

439

#!/usr/bin/env python 

""" 

python-patch test suite 

 

There are two kind of tests: 

- file-based tests 

- directory-based tests 

- unit tests 

 

File-based test is patch file, initial file and resulting file 

for comparison. 

 

Directory-based test is a self-sufficient directory with: 

files to be patched, patch file itself and [result] dir. You can 

manually apply patch and compare outcome with [result] directory. 

This is what this test runner does. 

 

Unit tests test API and are all inside this runner. 

 

 

== Code Coverage == 

 

To refresh code coverage stats, get 'coverage' tool from 

http://pypi.python.org/pypi/coverage/ and run this file with: 

 

  coverage run run_tests.py 

  coverage html -d coverage 

 

On Windows it may be more convenient instead of `coverage` call 

`python -m coverage.__main__` 

""" 

 

import os 

import sys 

import re 

import shutil 

import unittest 

import copy 

from os import listdir 

from os.path import abspath, dirname, exists, join, isdir, isfile 

from tempfile import mkdtemp 

 

verbose = False 

if "-v" in sys.argv or "--verbose" in sys.argv: 

  verbose = True 

 

 

# full path for directory with tests 

TESTS = dirname(abspath(__file__)) 

TESTDATA = join(TESTS, 'data') 

def testfile(name): 

  return join(TESTDATA, name) 

 

 

# import patch.py from parent directory 

save_path = sys.path 

sys.path.insert(0, dirname(TESTS)) 

import patch 

sys.path = save_path 

 

 

# ---------------------------------------------------------------------------- 

class TestPatchFiles(unittest.TestCase): 

  """ 

  unittest hack - test* methods are generated by add_test_methods() function 

  below dynamically using information about *.patch files from tests directory 

 

  """ 

  def _assert_files_equal(self, file1, file2): 

      f1 = f2 = None 

      try: 

        f1 = open(file1, "rb") 

        f2 = open(file2, "rb") 

        for line in f1: 

          self.assertEqual(line, f2.readline()) 

 

      finally: 

        if f2: 

          f2.close() 

        if f1: 

          f1.close() 

 

  def _assert_dirs_equal(self, dir1, dir2, ignore=[]): 

      """ 

      compare dir2 with reference dir1, ignoring entries 

      from supplied list 

 

      """ 

      # recursive 

      if type(ignore) == str: 

        ignore = [ignore] 

      e2list = [en for en in listdir(dir2) if en not in ignore] 

      for e1 in listdir(dir1): 

        if e1 in ignore: 

          continue 

        e1path = join(dir1, e1) 

        e2path = join(dir2, e1) 

        self.assert_(exists(e1path)) 

        self.assert_(exists(e2path), "%s does not exist" % e2path) 

        self.assert_(isdir(e1path) == isdir(e2path)) 

        if not isdir(e1path): 

          self._assert_files_equal(e1path, e2path) 

        else: 

          self._assert_dirs_equal(e1path, e2path, ignore=ignore) 

        e2list.remove(e1) 

      for e2 in e2list: 

        self.fail("extra file or directory: %s" % e2) 

 

 

  def _run_test(self, testname): 

      """ 

      boilerplate for running *.patch file tests 

      """ 

 

      # 1. create temp test directory 

      # 2. copy files 

      # 3. execute file-based patch  

      # 4. compare results 

      # 5. cleanup on success 

 

      tmpdir = mkdtemp(prefix="%s."%testname) 

 

      basepath = join(TESTS, testname) 

      basetmp = join(tmpdir, testname) 

 

      patch_file = basetmp + ".patch" 

 

      file_based = isfile(basepath + ".from") 

      from_tgt = basetmp + ".from" 

 

      if file_based: 

        shutil.copy(basepath + ".from", tmpdir) 

        shutil.copy(basepath + ".patch", tmpdir) 

      else: 

        # directory-based 

        for e in listdir(basepath): 

          epath = join(basepath, e) 

          if not isdir(epath): 

            shutil.copy(epath, join(tmpdir, e)) 

          else: 

            shutil.copytree(epath, join(tmpdir, e)) 

 

 

      # 3. 

      # test utility as a whole 

      patch_tool = join(dirname(TESTS), "patch.py") 

      save_cwd = os.getcwdu() 

      os.chdir(tmpdir) 

      if verbose: 

        cmd = '%s %s "%s"' % (sys.executable, patch_tool, patch_file) 

        print "\n"+cmd 

      else: 

        cmd = '%s %s -q "%s"' % (sys.executable, patch_tool, patch_file) 

      ret = os.system(cmd) 

      assert ret == 0, "Error %d running test %s" % (ret, testname) 

      os.chdir(save_cwd) 

 

 

      # 4. 

      # compare results 

      if file_based: 

        self._assert_files_equal(basepath + ".to", from_tgt) 

      else: 

        # recursive comparison 

        self._assert_dirs_equal(join(basepath, "[result]"), 

                                tmpdir, 

                                ignore=["%s.patch" % testname, ".svn", "[result]"]) 

 

 

      shutil.rmtree(tmpdir) 

      return 0 

 

 

def add_test_methods(cls): 

    """ 

    hack to generate test* methods in target class - one 

    for each *.patch file in tests directory 

    """ 

 

    # list testcases - every test starts with number 

    # and add them as test* methods 

    testptn = re.compile(r"^(?P<name>\d{2,}[^\.]+).*$") 

 

    testset = [testptn.match(e).group('name') for e in listdir(TESTS) if testptn.match(e)] 

    testset = sorted(set(testset)) 

 

    for filename in testset: 

      methname = 'test_' + filename 

      def create_closure(): 

        name = filename 

        return lambda self: self._run_test(name) 

      test = create_closure() 

      setattr(cls, methname, test) 

      if verbose: 

        print "added test method %s to %s" % (methname, cls) 

add_test_methods(TestPatchFiles) 

 

# ---------------------------------------------------------------------------- 

 

class TestCheckPatched(unittest.TestCase): 

    def setUp(self): 

        self.save_cwd = os.getcwdu() 

        os.chdir(TESTS) 

 

    def tearDown(self): 

        os.chdir(self.save_cwd) 

 

    def test_patched_multipatch(self): 

        pto = patch.fromfile("01uni_multi/01uni_multi.patch") 

        os.chdir(join(TESTS, "01uni_multi", "[result]")) 

        self.assert_(pto.can_patch("updatedlg.cpp")) 

 

    def test_can_patch_single_source(self): 

        pto2 = patch.fromfile("02uni_newline.patch") 

        self.assert_(pto2.can_patch("02uni_newline.from")) 

 

    def test_can_patch_fails_on_target_file(self): 

        pto3 = patch.fromfile("03trail_fname.patch") 

        self.assertEqual(None, pto3.can_patch("03trail_fname.to")) 

        self.assertEqual(None, pto3.can_patch("not_in_source.also")) 

 

    def test_multiline_false_on_other_file(self): 

        pto = patch.fromfile("01uni_multi/01uni_multi.patch") 

        os.chdir(join(TESTS, "01uni_multi")) 

        self.assertFalse(pto.can_patch("updatedlg.cpp")) 

 

    def test_single_false_on_other_file(self): 

        pto3 = patch.fromfile("03trail_fname.patch") 

        self.assertFalse(pto3.can_patch("03trail_fname.from")) 

 

    def test_can_patch_checks_source_filename_even_if_target_can_be_patched(self): 

        pto2 = patch.fromfile("04can_patch.patch") 

        self.assertFalse(pto2.can_patch("04can_patch.to")) 

 

# ---------------------------------------------------------------------------- 

 

class TestPatchParse(unittest.TestCase): 

    def test_fromstring(self): 

        try: 

          f = open(join(TESTS, "01uni_multi/01uni_multi.patch"), "rb") 

          readstr = f.read() 

        finally: 

          f.close() 

        pst = patch.fromstring(readstr) 

        self.assertEqual(len(pst), 5) 

 

    def test_fromfile(self): 

        pst = patch.fromfile(join(TESTS, "01uni_multi/01uni_multi.patch")) 

        self.assertNotEqual(pst, False) 

        self.assertEqual(len(pst), 5) 

        ps2 = patch.fromfile(testfile("failing/not-a-patch.log")) 

        self.assertFalse(ps2) 

 

    def test_no_header_for_plain_diff_with_single_file(self): 

        pto = patch.fromfile(join(TESTS, "03trail_fname.patch")) 

        self.assertEqual(pto.items[0].header, []) 

 

    def test_header_for_second_file_in_svn_diff(self): 

        pto = patch.fromfile(join(TESTS, "01uni_multi/01uni_multi.patch")) 

        self.assertEqual(pto.items[1].header[0], 'Index: updatedlg.h\r\n') 

        self.assert_(pto.items[1].header[1].startswith('=====')) 

 

    def test_hunk_desc(self): 

        pto = patch.fromfile(testfile('git-changed-file.diff')) 

        self.assertEqual(pto.items[0].hunks[0].desc, 'class JSONPluginMgr(object):') 

 

    def test_autofixed_absolute_path(self): 

        pto = patch.fromfile(join(TESTS, "data/autofix/absolute-path.diff")) 

        self.assertEqual(pto.errors, 0) 

        self.assertEqual(pto.warnings, 2) 

        self.assertEqual(pto.items[0].source, "winnt/tests/run_tests.py") 

 

    def test_autofixed_parent_path(self): 

        # [ ] exception vs return codes for error recovery 

        #  [x] separate return code when patch lib compensated the error 

        #      (implemented as warning count) 

        pto = patch.fromfile(join(TESTS, "data/autofix/parent-path.diff")) 

        self.assertEqual(pto.errors, 0) 

        self.assertEqual(pto.warnings, 2) 

        self.assertEqual(pto.items[0].source, "patch.py") 

 

    def test_autofixed_stripped_trailing_whitespace(self): 

        pto = patch.fromfile(join(TESTS, "data/autofix/stripped-trailing-whitespace.diff")) 

        self.assertEqual(pto.errors, 0) 

        self.assertEqual(pto.warnings, 4) 

 

    def test_fail_missing_hunk_line(self): 

        fp = open(join(TESTS, "data/failing/missing-hunk-line.diff")) 

        pto = patch.PatchSet() 

        self.assertNotEqual(pto.parse(fp), True) 

        fp.close() 

 

    def test_fail_context_format(self): 

        fp = open(join(TESTS, "data/failing/context-format.diff")) 

        res = patch.PatchSet().parse(fp) 

        self.assertFalse(res) 

        fp.close() 

 

    def test_fail_not_a_patch(self): 

        fp = open(join(TESTS, "data/failing/not-a-patch.log")) 

        res = patch.PatchSet().parse(fp) 

        self.assertFalse(res) 

        fp.close() 

 

    def test_diffstat(self): 

        output = """\ 

updatedlg.cpp | 20 ++++++++++++++++++-- 

updatedlg.h   |  1 + 

manifest.xml  | 15 ++++++++------- 

conf.cpp      | 23 +++++++++++++++++------ 

conf.h        |  7 ++++--- 

5 files changed, 48 insertions(+), 18 deletions(-), +1203 bytes""" 

        pto = patch.fromfile(join(TESTS, "01uni_multi/01uni_multi.patch")) 

        self.assertEqual(pto.diffstat(), output, "Output doesn't match") 

 

 

class TestPatchSetDetection(unittest.TestCase): 

    def test_svn_detected(self): 

        pto = patch.fromfile(join(TESTS, "01uni_multi/01uni_multi.patch")) 

        self.assertEqual(pto.type, patch.SVN) 

 

# generate tests methods for TestPatchSetDetection - one for each patch file 

def generate_detection_test(filename, patchtype): 

  # saving variable in local scope to prevent test() 

  # from fetching it from global 

  patchtype = difftype 

  def test(self): 

    pto = patch.fromfile(join(TESTDATA, filename)) 

    self.assertEqual(pto.type, patchtype) 

  return test 

 

for filename in os.listdir(TESTDATA): 

  if isdir(join(TESTDATA, filename)): 

    continue 

 

  difftype = patch.PLAIN 

  if filename.startswith('git-'): 

    difftype = patch.GIT 

  if filename.startswith('hg-'): 

    difftype = patch.HG 

  if filename.startswith('svn-'): 

    difftype = patch.SVN 

 

  name = 'test_'+filename 

  test = generate_detection_test(filename, difftype) 

  setattr(TestPatchSetDetection, name, test) 

  if verbose: 

    print "added test method %s to %s" % (name, 'TestPatchSetDetection') 

 

 

class TestPatchApply(unittest.TestCase): 

    def setUp(self): 

        self.save_cwd = os.getcwdu() 

        self.tmpdir = mkdtemp(prefix=self.__class__.__name__) 

        os.chdir(self.tmpdir) 

 

    def tearDown(self): 

        os.chdir(self.save_cwd) 

        shutil.rmtree(self.tmpdir) 

 

    def tmpcopy(self, filenames): 

        """copy file(s) from test_dir to self.tmpdir""" 

        for f in filenames: 

          shutil.copy(join(TESTS, f), self.tmpdir) 

 

    def test_apply_returns_false_on_failure(self): 

        self.tmpcopy(['data/failing/non-empty-patch-for-empty-file.diff', 

                      'data/failing/upload.py']) 

        pto = patch.fromfile('non-empty-patch-for-empty-file.diff') 

        self.assertFalse(pto.apply()) 

 

    def test_apply_returns_true_on_success(self): 

        self.tmpcopy(['03trail_fname.patch', 

                      '03trail_fname.from']) 

        pto = patch.fromfile('03trail_fname.patch') 

        self.assert_(pto.apply()) 

 

    def test_revert(self): 

        self.tmpcopy(['03trail_fname.patch', 

                      '03trail_fname.from']) 

        pto = patch.fromfile('03trail_fname.patch') 

        self.assert_(pto.apply()) 

        self.assertNotEqual(open(self.tmpdir + '/03trail_fname.from').read(), 

                            open(TESTS + '/03trail_fname.from').read()) 

        self.assert_(pto.revert()) 

        self.assertEqual(open(self.tmpdir + '/03trail_fname.from').read(), 

                         open(TESTS + '/03trail_fname.from').read()) 

 

    def test_apply_root(self): 

        treeroot = join(self.tmpdir, 'rootparent') 

        shutil.copytree(join(TESTS, '06nested'), treeroot) 

        pto = patch.fromfile(join(TESTS, '06nested/06nested.patch')) 

        self.assert_(pto.apply(root=treeroot)) 

 

    def test_apply_strip(self): 

        treeroot = join(self.tmpdir, 'rootparent') 

        shutil.copytree(join(TESTS, '06nested'), treeroot) 

        pto = patch.fromfile(join(TESTS, '06nested/06nested.patch')) 

        for p in pto: 

          p.source = 'nasty/prefix/' + p.source 

          p.target = 'nasty/prefix/' + p.target 

        self.assert_(pto.apply(strip=2, root=treeroot)) 

 

 

class TestHelpers(unittest.TestCase): 

    # unittest setting 

    longMessage = True 

 

    absolute = ['/', 'c:\\', 'c:/', '\\', '/path', 'c:\\path'] 

    relative = ['path', 'path:\\', 'path:/', 'path\\', 'path/', 'path\\path'] 

 

    def test_xisabs(self): 

        for path in self.absolute: 

            self.assertTrue(patch.xisabs(path), 'Target path: ' + repr(path)) 

        for path in self.relative: 

            self.assertFalse(patch.xisabs(path), 'Target path: ' + repr(path)) 

 

    def test_xnormpath(self): 

        path = "../something/..\\..\\file.to.patch" 

        self.assertEqual(patch.xnormpath(path), '../../file.to.patch') 

 

    def test_xstrip(self): 

        for path in self.absolute[:4]: 

            self.assertEqual(patch.xstrip(path), '') 

        for path in self.absolute[4:6]: 

            self.assertEqual(patch.xstrip(path), 'path') 

        # test relative paths are not affected 

        for path in self.relative: 

            self.assertEqual(patch.xstrip(path), path) 

 

    def test_pathstrip(self): 

        self.assertEqual(patch.pathstrip('path/to/test/name.diff', 2), 'test/name.diff') 

        self.assertEqual(patch.pathstrip('path/name.diff', 1), 'name.diff') 

        self.assertEqual(patch.pathstrip('path/name.diff', 0), 'path/name.diff') 

 

# ---------------------------------------------------------------------------- 

 

if __name__ == '__main__': 

    unittest.main()