2 """Test various aspects of StGit packaging.
4 [x] Use XDG_RUNTIME_DIR
7 [ ] Make annotated tags within test repo
8 [x] Different python versions
9 [ ] Different setuptools versions
11 Test source distribution creation:
12 [x] Ensure generated files are in sdist tarball
13 [x] Ensure that generated files are not in worktree
14 [x] Ensure dirty worktree is reflected in version
17 [x] Ensure generated python files land in build/
18 [x] Ensure generated python files also land in worktree
19 [x] Ensure generated completions land in worktree
21 Test install from git worktree:
23 [x] Test installing to virtualenv
24 [ ] Test installing to system (i.e. w/chroot)
26 Test install from unpacked sdist tarball
27 [x] Test installing to virtualenv
28 [ ] Test installing to system (i.e. w/chroot)
30 [x] Test install from git URL
32 Test install sdist tarball with pip
33 [x] Ensure generated python is in right place
34 [x] Ensure generated completions in right place
35 [x] Ensure other files in right places
37 Test making sdist from sdist
38 [x] Make sdist from git worktree
40 [x] Make sdist from unpacked sdist tree
41 [x] Ensure identical tarballs
44 [x] Test build with bdist_wheel
45 [x] Test install whl to virtualenv
48 [x] Test generate git archive
49 [x] Test make sdist from git archive
50 [ ] Test install from unpacked git archive
52 Test running test suite
55 [ ] with installed stg
66 from pathlib
import Path
77 BUILD_PACKAGE_NAMES
= ['pip', 'wheel', 'setuptools']
84 def log_test(testname
, *args
):
85 print('\n###', testname
, *args
)
88 def printable_cmd(cmd_list
):
89 runtime_dir
= Path(os
.environ
['XDG_RUNTIME_DIR'])
93 if isinstance(item
, Path
):
94 if item
.is_relative_to(runtime_dir
):
95 printable
= f
'{item.relative_to(runtime_dir)}'
96 elif item
.is_relative_to(home
):
97 printable
= f
'~/{item.relative_to(home)}'
100 printable_items
.append(printable
)
102 printable_items
.append(item
)
103 return ' '.join(printable_items
)
106 def subprocess_cmd(cmd_list
):
108 for item
in cmd_list
:
109 if isinstance(item
, Path
):
110 norm_cmd
.append(str(item
))
112 norm_cmd
.append(item
)
116 def call(cmd
, cwd
, **kwargs
):
117 print('!!!', printable_cmd(cmd
), f
'(in {printable_cmd([cwd])})')
118 stdout
= kwargs
.get('stdout')
120 proc
= subprocess
.run(
123 stdout
=subprocess
.PIPE
,
124 stderr
=subprocess
.STDOUT
,
126 for line
in proc
.stdout
.decode().splitlines():
128 return proc
.returncode
130 return subprocess
.call(subprocess_cmd(cmd
), cwd
=cwd
, **kwargs
)
133 def check_call(cmd
, cwd
, **kwargs
):
134 print('!!!', printable_cmd(cmd
), f
'(in {printable_cmd([cwd])})')
135 stdout
= kwargs
.get('stdout')
137 proc
= subprocess
.run(
140 stdout
=subprocess
.PIPE
,
141 stderr
=subprocess
.STDOUT
,
143 for line
in proc
.stdout
.decode().splitlines():
145 proc
.check_returncode()
147 subprocess
.check_call(subprocess_cmd(cmd
), cwd
=cwd
, **kwargs
)
150 def check_output(cmd
, cwd
):
151 print('!!!', printable_cmd(cmd
), f
'(in {printable_cmd([cwd])})')
152 return subprocess
.check_output(subprocess_cmd(cmd
), cwd
=cwd
)
156 parser
= argparse
.ArgumentParser()
157 parser
.set_defaults(pythons
=[])
158 for pyver
in PYTHON_VERSIONS
:
160 f
'--{pyver}', dest
='pythons', action
='append_const', const
=pyver
162 parser
.add_argument('--all-pythons', action
='store_true')
163 args
= parser
.parse_args()
166 args
.pythons
[:] = PYTHON_VERSIONS
167 elif not args
.pythons
:
168 args
.pythons
.append(PYTHON_VERSIONS
[0])
170 assert 'VIRTUAL_ENV' not in os
.environ
, 'Do not run this from a virtual env'
172 system_stg
= shutil
.which('stg')
173 log(f
'{system_stg=}')
174 if system_stg
is None:
175 system_stg_version
= None
177 system_stg_version
= version_from_stg_version(
178 check_output(['stg', '--version'], cwd
=Path('/')).decode()
180 log(f
'{system_stg_version=}')
183 check_output(['git', 'rev-parse', '--show-toplevel'], cwd
=Path())
188 assert this_repo
.is_absolute()
190 runtime_dir
= Path(os
.environ
['XDG_RUNTIME_DIR'])
191 log(f
'{runtime_dir=}')
192 test_root
= runtime_dir
/ 'stgit-pkgtest'
194 assert test_root
.is_absolute()
195 if test_root
.exists():
196 shutil
.rmtree(test_root
)
197 test_root
.mkdir(exist_ok
=False)
199 cache_root
= runtime_dir
/ 'stgit-pkgcache'
200 should_create_cache
= not cache_root
.exists()
201 log(f
'{cache_root=} {should_create_cache=}')
202 assert cache_root
.is_absolute()
204 if should_create_cache
:
205 create_cache(cache_root
)
207 for python
in args
.pythons
:
208 base
= test_root
/ python
209 repo
= prepare_test_repo(base
, this_repo
)
210 prepare_virtual_env(base
, python
, cache_root
)
211 test_dirty_version(base
, repo
)
212 test_install_from_git_url(base
, repo
, cache_root
)
213 test_uninstall_from_venv(base
)
214 test_sdist_creation(base
, repo
)
215 test_create_git_archive(base
, repo
)
216 test_build(base
, repo
)
217 test_bdist_wheel(base
, repo
)
218 test_develop_mode(base
, repo
, cache_root
)
219 test_uninstall_from_venv(base
)
220 test_running_tests_from_sdist(base
)
221 test_install_from_unpacked_sdist(base
, cache_root
)
222 test_uninstall_from_venv(base
)
223 test_install_sdist(base
, cache_root
)
224 test_uninstall_from_venv(base
)
225 test_install_wheel(base
)
226 test_uninstall_from_venv(base
)
229 def venv_py_exe(base
):
230 return base
/ 'venv' / 'bin' / 'python'
233 def venv_stg_exe(base
):
234 return base
/ 'venv' / 'bin' / 'stg'
237 def version_from_sdist(tgz_path
):
238 return tgz_path
.name
.split('stgit-', 1)[1].rsplit('.tar.gz', 1)[0]
241 def version_from_wheel(whl_path
):
242 return whl_path
.name
.split('stgit-', 1)[1].rsplit('-py3-none-any.whl')[0]
245 def version_from_stg_version(output
):
246 for line
in output
.splitlines():
247 if line
.lower().startswith('stacked git'):
251 def create_cache(cache_root
):
252 log_test('create_cache')
255 [sys
.executable
, '-m', 'pip', 'download'] + BUILD_PACKAGE_NAMES
,
259 for package_name
in BUILD_PACKAGE_NAMES
:
260 wheel_paths
= list(cache_root
.glob(f
'{package_name}-*.whl'))
261 assert len(wheel_paths
) == 1, wheel_paths
262 wheel_path
= wheel_paths
[0]
263 wheel_link
= cache_root
/ f
'{package_name}.whl'
264 wheel_link
.symlink_to(wheel_path
)
267 def prepare_test_repo(base
, this_repo
):
268 log_test('prepare_test_repo')
269 test_repo
= base
/ 'stgit-git'
271 ['git', 'clone', '--quiet', '--local', '--no-hardlinks', this_repo
, test_repo
],
278 def prepare_virtual_env(base
, python
, cache_root
):
279 log_test('prepare_virtual_env')
280 venv_path
= base
/ 'venv'
281 check_call([python
, '-m', 'venv', venv_path
], cwd
=base
)
283 (cache_root
/ f
'{package_name}.whl').resolve()
284 for package_name
in BUILD_PACKAGE_NAMES
287 [venv_py_exe(base
), '-m', 'pip', 'install', '--no-index'] + packages
,
291 [venv_py_exe(base
), '-m', 'pip', 'list', '--no-index', '--format=freeze'],
298 def test_dirty_version(base
, repo
):
299 log_test('test_dirty_version')
301 # Modify a checked-in file to make worktree dirty
302 dirty_file
= repo
/ 'README.md'
303 with
open(dirty_file
, 'a') as f
:
304 print("DIRTY STUFF", file=f
)
306 # Make sdist from dirty repo
308 [venv_py_exe(base
), 'setup.py', 'sdist'],
310 stdout
=subprocess
.DEVNULL
,
312 sdist_paths
= list((repo
/ 'dist').glob('stgit-*.tar.gz'))
313 assert len(sdist_paths
) == 1
314 stgit_tgz
= sdist_paths
[0]
317 # Ensure sdist's version is marked 'dirty'
318 version
= version_from_sdist(stgit_tgz
)
320 assert 'dirty' in version
322 # Restore worktree to pristine state
323 check_call(['git', 'checkout', '--', dirty_file
], cwd
=repo
)
324 check_call(['git', 'diff', '--quiet'], cwd
=repo
)
325 check_call(['git', 'clean', '-qfx'], cwd
=repo
)
328 def test_install_from_git_url(base
, repo
, cache
):
329 log_test('test_install_from_git_url')
340 f
'git+file://{str(repo)}',
344 version
= version_from_stg_version(
345 check_output([venv_py_exe(base
), '-m', 'stgit', '--version'], cwd
=base
).decode()
349 # Ensure cmdlist.py was generated
350 check_call([venv_py_exe(base
), '-c', 'import stgit.commands.cmdlist'], cwd
=base
)
353 def test_sdist_creation(base
, repo
):
354 log_test('test_sdist_creation')
357 [venv_py_exe(base
), 'setup.py', 'sdist', '--dist-dir', dist
],
359 stdout
=subprocess
.DEVNULL
,
361 sdist_paths
= list(dist
.glob('stgit-*.tar.gz'))
362 assert len(sdist_paths
) == 1
363 stgit_tgz
= sdist_paths
[0]
365 prefix
= Path(stgit_tgz
.with_suffix('').with_suffix('').name
)
366 version
= version_from_sdist(stgit_tgz
)
369 # Ensure generated files are in sdist tarball
370 with tarfile
.open(stgit_tgz
) as tf
:
371 tf
.getmember(str(prefix
/ 'completion' / 'stg.fish'))
372 tf
.getmember(str(prefix
/ 'completion' / 'stgit.bash'))
373 tf
.getmember(str(prefix
/ 'stgit' / 'commands' / 'cmdlist.py'))
374 _version_tar_info
= tf
.getmember(str(prefix
/ 'stgit' / '_version.py'))
375 assert 'def _get_version()' not in _version_tar_info
.tobuf().decode()
377 # Ensure that no checked-in files in the working tree were altered
378 check_call(['git', 'diff', '--quiet'], cwd
=repo
)
380 # Extract existing sdist from base/dist
381 orig_sdist
= base
/ 'orig-sdist'
383 work_sdist
= base
/ 'work-sdist'
385 with tarfile
.open(stgit_tgz
) as tf
:
386 tf
.extractall(orig_sdist
)
387 tf
.extractall(work_sdist
)
389 # Create an sdist from an sdist
390 dist2
= base
/ 'dist2'
392 [venv_py_exe(base
), 'setup.py', 'sdist', '--dist-dir', dist2
],
393 cwd
=work_sdist
/ prefix
,
394 stdout
=subprocess
.DEVNULL
,
396 for pycache
in work_sdist
.rglob('**/__pycache__'):
397 shutil
.rmtree(pycache
)
399 # Ensure derivative sdist is same as original sdist
400 rc
= call(['diff', '-qr', orig_sdist
, work_sdist
], cwd
=base
)
402 call(['diff', '-uNr', orig_sdist
, work_sdist
], cwd
=base
)
403 raise Exception('Recursive sdist is different')
406 def test_create_git_archive(base
, repo
):
407 log_test('test_create_git_archive')
408 archive_tgz
= base
/ 'stgit-archive.tar.gz'
410 ['git', 'archive', '--prefix', 'stgit-archive/', '-o', archive_tgz
, 'HEAD'],
414 with tarfile
.open(archive_tgz
) as tf
:
417 archive_dir
= base
/ 'stgit-archive'
419 assert archive_dir
.is_dir()
421 dist
= base
/ 'dist-archive'
424 [venv_py_exe(base
), 'setup.py', 'sdist', '--dist-dir', dist
],
426 stdout
=subprocess
.DEVNULL
,
428 sdist_paths
= list(dist
.glob('stgit-*.tar.gz'))
429 assert len(sdist_paths
) == 1
430 stgit_tgz
= sdist_paths
[0]
432 prefix
= Path(stgit_tgz
.with_suffix('').with_suffix('').name
)
433 version
= version_from_sdist(stgit_tgz
)
436 # Ensure generated files are in sdist tarball
437 with tarfile
.open(stgit_tgz
) as tf
:
438 tf
.getmember(str(prefix
/ 'completion' / 'stg.fish'))
439 tf
.getmember(str(prefix
/ 'completion' / 'stgit.bash'))
440 tf
.getmember(str(prefix
/ 'stgit' / 'commands' / 'cmdlist.py'))
441 _version_tar_info
= tf
.getmember(str(prefix
/ 'stgit' / '_version.py'))
442 assert 'def _get_version()' not in _version_tar_info
.tobuf().decode()
445 def test_build(base
, repo
):
446 log_test('test_build')
447 build
= base
/ 'build'
449 [venv_py_exe(base
), 'setup.py', 'build', '--build-base', build
],
451 stdout
=subprocess
.DEVNULL
,
454 # Ensure generated files exist in build tree
455 assert (build
/ 'lib' / 'stgit').is_dir()
456 assert (build
/ 'lib' / 'stgit' / 'templates').is_dir()
457 assert (build
/ 'lib' / 'stgit' / 'templates' / 'patchmail.tmpl').is_file()
458 assert (build
/ 'lib' / 'stgit' / 'commands' / 'cmdlist.py').is_file()
460 # _version.py file in the built tree should be written with a static version
463 not in (build
/ 'lib' / 'stgit' / '_version.py').read_text()
466 # Ensure that generated artifacts also exist in worktree
467 assert (repo
/ 'stgit' / 'commands' / 'cmdlist.py').is_file()
468 assert (repo
/ 'completion' / 'stg.fish').is_file()
469 assert (repo
/ 'completion' / 'stgit.bash').is_file()
471 # The _version.py in the worktree should never be modified
472 assert 'def _get_version()' in (repo
/ 'stgit' / '_version.py').read_text()
474 # Ensure no checked-in files have been modified by the build
475 check_call(['git', 'diff', '--quiet'], cwd
=repo
)
478 def test_bdist_wheel(base
, repo
):
479 log_test('test_bdist_wheel')
480 dist
= base
/ 'dist-whl'
482 [venv_py_exe(base
), 'setup.py', 'bdist_wheel', '--dist-dir', dist
],
484 stdout
=subprocess
.DEVNULL
,
486 sdist_paths
= list(dist
.glob('stgit-*.whl'))
487 assert len(sdist_paths
) == 1
488 stgit_whl
= sdist_paths
[0]
490 version
= version_from_wheel(stgit_whl
)
493 # Ensure generated files exist in .whl file
494 with zipfile
.ZipFile(stgit_whl
, 'r') as zf
:
495 zf
.getinfo('stgit/commands/cmdlist.py')
496 assert 'def _get_version' not in zf
.read('stgit/_version.py').decode()
497 zf
.getinfo('stgit/templates/patchmail.tmpl')
500 def test_develop_mode(base
, repo
, cache
):
501 log_test('test_develop_mode')
502 # PEP-518 build isolation means build-time deps are installed even
503 # if they're already installed (i.e. already installed in the
504 # virtual env). Thus --find-links is used with --no-index so that
505 # wheel and setuptools can be installed without going to the network.
521 # Gather versions in various ways and ensure they are all consistent
522 py_c_stgit_version
= (
524 [venv_py_exe(base
), '-c', 'import stgit; print(stgit.get_version())'],
530 py_m_stgit_version
= version_from_stg_version(
531 check_output([venv_py_exe(base
), '-m', 'stgit', '--version'], cwd
=base
).decode()
534 py_c_stgit_version
== py_m_stgit_version
535 ), f
'{py_c_stgit_version=} {py_m_stgit_version=}'
537 venv_stg_version
= version_from_stg_version(
538 check_output([venv_stg_exe(base
), '--version'], cwd
=base
).decode()
541 py_c_stgit_version
== venv_stg_version
542 ), f
'{py_c_stgit_version=} {venv_stg_version=}'
544 log(f
'{py_c_stgit_version=}')
545 assert 'dirty' not in py_m_stgit_version
547 # Modify a checked-in file to make worktree dirty
548 dirty_file
= repo
/ 'README.md'
549 with
open(dirty_file
, 'a') as f
:
550 print("DIRTY STUFF", file=f
)
552 py_c_dirty_version
= (
554 [venv_py_exe(base
), '-c', 'import stgit; print(stgit.get_version())'],
560 py_m_dirty_version
= version_from_stg_version(
561 check_output([venv_py_exe(base
), '-m', 'stgit', '--version'], cwd
=base
).decode()
564 py_c_dirty_version
== py_m_dirty_version
565 ), f
'{py_c_dirty_version=} {py_m_dirty_version=}'
567 venv_std_dirty_version
= version_from_stg_version(
568 check_output([venv_stg_exe(base
), '--version'], cwd
=base
).decode()
571 py_c_dirty_version
== venv_std_dirty_version
572 ), f
'{py_c_dirty_version=} {venv_std_dirty_version=}'
574 log(f
'{py_c_dirty_version=}')
575 assert 'dirty' in py_m_dirty_version
576 assert f
'{py_m_stgit_version}.dirty' == py_m_dirty_version
578 # Restore worktree to pristine state
579 check_call(['git', 'checkout', '--', dirty_file
], cwd
=repo
)
580 check_call(['git', 'diff', '--quiet'], cwd
=repo
)
581 check_call(['git', 'clean', '-qfx'], cwd
=repo
)
584 def test_uninstall_from_venv(base
):
585 log_test('test_uninstall_from_venv')
587 [venv_py_exe(base
), '-m', 'pip', 'uninstall', '--yes', 'stgit'], cwd
=base
590 venv_path
= base
/ 'venv'
591 assert not (venv_path
/ 'bin' / 'stg').exists()
592 site_packages
= next((venv_path
/ 'lib').glob('python3.*')) / 'site-packages'
593 assert site_packages
.is_dir()
594 assert not list(site_packages
.glob('stgit*'))
595 share_dir
= venv_path
/ 'share' / 'stgit'
596 assert not share_dir
.exists() or not list(share_dir
.iterdir())
599 def test_running_tests_from_sdist(base
):
600 log_test('test_running_tests_from_sdist')
601 work_sdist
= next((base
/ 'work-sdist').iterdir())
602 check_call(['./t0000-init.sh'], cwd
=work_sdist
/ 't')
605 def test_install_from_unpacked_sdist(base
, cache
):
606 log_test('test_install_from_unpacked_sdist')
607 work_sdist
= next((base
/ 'work-sdist').iterdir())
622 version
= version_from_stg_version(
623 check_output([venv_py_exe(base
), '-m', 'stgit', '--version'], cwd
=base
).decode()
625 assert work_sdist
.name
.endswith(version
)
628 def test_install_sdist(base
, cache
):
629 log_test('test_install_sdist')
630 sdist_path
= next((base
/ 'dist').iterdir())
644 version
= version_from_stg_version(
645 check_output([venv_py_exe(base
), '-m', 'stgit', '--version'], cwd
=base
).decode()
647 assert sdist_path
.name
.split('stgit-', 1)[1].startswith(version
)
650 def test_install_wheel(base
):
651 log_test('test_install_wheel')
652 whl_path
= next((base
/ 'dist-whl').iterdir())
655 [venv_py_exe(base
), '-m', 'pip', 'install', '--no-index', whl_path
],
658 version
= version_from_stg_version(
659 check_output([venv_py_exe(base
), '-m', 'stgit', '--version'], cwd
=base
).decode()
661 assert whl_path
.name
.split('stgit-', 1)[1].startswith(version
)
664 if __name__
== '__main__':