. "$BST_LIB" # BST-TIMEOUT: 1800 # TWO python3 starts, and they are the bulk of the cost. # # Every other case here reads the filesystem or dpkg with # # shell builtins, or greps the second start's output. # # # # The two are: a bare `import' (one case, so an import # # failure is legible without reading a transcript) and the # # probe, which takes ONE start for a hundred-odd # # measurements. # # # # THE PROBE ALSO SPAWNS SHORT /bin/sh CHILDREN -- about a # # dozen of them, each a two-line script that prints one # # line and exits. They are what stands in for `git # # describe', and they are why this budget is 1800 rather # # than the 1200 a pure-python probe of this size takes: # # a process start on this arm has not been measured for # # this suite, and a suite-timeout red is a batch-wide cost, # # so the number is deliberately generous. # # # # NOTHING HERE SLEEPS, opens a socket, starts a thread or # # reaches a device, a peer or a terminal. Every fixture is # # written by the probe into its own directory under # # $TMPDIR and removed again. # # # # NOT MEASURED ON TARGET YET: this port's first on-target # # run is the batch verify, and the measured elapsed # # replaces this note in the test plan rather than in this # # comment, which would cost an image build to keep the # # shipped suite byte-identical. # # python3-vcversioner 2.16.0.0 (poky meta, # recipes-devtools/python/python3-vcversioner_2.16.0.0.bb, `inherit pypi # setuptools3', PyPI sdist) on the wasm32-hwjs glibc image. # # ============================================================================ # WHAT THE PACKAGE DOES, AND WHAT THAT MAKES THE SUITE ASSERT. # ============================================================================ # # One module, one interesting function. `find_version()' answers the question # "what version is this source tree?" from version-control metadata, and # because that metadata is gone by the time an sdist is unpacked, it also # WRITES the answer into a `version.txt' so the next caller can read it back. # # So the package IS a fallback chain, and every behavioural case below asserts # one link of it explicitly: # # 1. a VCS command, if one was supplied or auto-detected -- its stdout is the # version, and it is written into version.txt; # 2. failing that, version.txt, if it exists -- read, parsed, and rewritten; # 3. failing that, `SystemExit(2)' with a printed explanation. # # The third link is the one worth insisting on: the documented behaviour is a # HARD FAILURE, not a silent `0.0.0' that would compare wrongly against every # real release for the rest of the package's life. `nothing-exit', # `unparsable-exit', `empty-file-exit' and `two-parts-exit' are the four shapes # of it. # # ---------------------------------------------------------------------------- # THE EXIT STATUS OF THE VCS COMMAND IS NEVER LOOKED AT. # ---------------------------------------------------------------------------- # # This is the thing about the package a reader is most likely to get wrong. # `find_version' runs the command, takes `stdout.strip()', and decides the # command "failed" IF AND ONLY IF that string is empty -- the return code is # read nowhere. So a wrapper that prints a usable version and exits 3 is # believed (`rc-ignored-version'), and one that exits 0 printing nothing is # not. Both halves are asserted, because either alone is equally consistent # with a status check that happened to agree. # # ---------------------------------------------------------------------------- # NO VCS IS INSTALLED FOR THIS SUITE, AND NONE IS NEEDED. # ---------------------------------------------------------------------------- # # Where a case needs a VCS the probe writes a two-line /bin/sh script that # prints what `git describe --tags --long' would print, and passes it as # `vcs_args'. That is a supported entry point -- the docstring specifies # vcs_args as "a list of string arguments including the program to run" -- so # the case tests the real code path rather than a mock of it, and it does not # depend on git or hg being on the image. # # The one case that DOES exercise auto-detection (`git-dir-exit') creates an # empty `.git' DIRECTORY, which selects the built-in git command. That command # then fails whether git is installed (it errors: not a repository) or absent # (Popen raises OSError, which find_version catches). Both answers are the same # exit 2, which is why the case is stable on an image whose git status is not # known here. The git and hg command lines themselves are asserted as DATA, # from the module's own `_vcs_args_by_path' table. # # ---------------------------------------------------------------------------- # WHERE THE EXPECTATIONS COME FROM. # ---------------------------------------------------------------------------- # # Every behavioural value asserted below was MEASURED on an x86-64 control at # the same version -- vcversioner 2.16.0.0 from the sdist whose sha256 the # recipe pins, under CPython 3.12.3, the same 3.12 this image ships. The # control was then re-run against THIS BUILD's own .deb payload and produced a # transcript identical apart from the module's path, and that payload's # `vcversioner.py' is byte-identical to the sdist's (9779 bytes, `cmp' clean). # So a red here is this arm, not a version skew and not a repackaging. # # ---------------------------------------------------------------------------- # WHAT IS PREDICTED RATHER THAN MEASURED, AND SO MIGHT BE RED. # ---------------------------------------------------------------------------- # # EXPECTED, NOT YET MEASURED -- this is a prediction and is written as one. The # recipe phase cannot build the image and so cannot run this suite; its first # on-target run is the batch verify. # # The exposure is `subprocess.Popen'. Roughly a dozen cases here fork and exec # /bin/sh, and process creation on this arm is the part of the platform this # port leans on that a pure-python package does not. If it is unavailable the # failure is NOT expected to be a hang or a crash, because find_version catches # OSError from Popen and falls through to the version.txt link of the chain -- # so the predicted shape of a bad outcome is a handful of reds among # `vcs-*', `rc-*' and `root-subst-*' with the file-driven cases still green, # and the version.txt half of the ticket's subject still demonstrated. That is # a prediction about the shape, not a claim about the result. # # Nothing else here is predicted to be arm-sensitive: the parse, the # arithmetic, the string handling and the file writes are pure python over # files in $TMPDIR. P=python3-vcversioner # ============================================================================ # The suite proper. # ============================================================================ # # `suite-canary' is emitted FIRST and unconditionally. A suite that dies before # its first result line is indistinguishable from one that was never scheduled, # so this one line is what separates "ran and found nothing" from "did not # run". bst_ok $P/suite-canary require python3 $P/python3-present SITE=/usr/lib/python3.12/site-packages MOD=$SITE/vcversioner.py DI=$SITE/vcversioner-2.16.0.0.dist-info PKGLIST=/var/lib/dpkg/info/python3-vcversioner.list PROBE=$BST_DATA/python3-vcversioner-probe.py tmp=${TMPDIR:-/tmp} W=$tmp/bst-vcversioner.$$ mkdir -p "$W" || { bst_notok $P/workdir "cannot create $W"; exit 0; } # ---------------------------------------------------------------- payload --- # WHERE THE PACKAGING SAYS THE MODULE LANDS. These run before any import, so a # red here says the .deb is wrong rather than that the module misbehaves. check $P/site-packages-dir test -d "$SITE" check $P/module-present test -s "$MOD" # IT IS A SINGLE MODULE, NOT A PACKAGE. setup.py declares `py_modules=' rather # than `packages=', so the payload is one top-level `vcversioner.py' and there # is no `vcversioner/' directory beside it. Asserted BOTH ways, because a # release that became a package would relocate the whole payload while every # behavioural case below still passed. check $P/not-a-package test ! -d "$SITE/vcversioner" # THE SIZE IS PINNED RATHER THAN FLOORED: a truncated or partially written # payload passes `test -s' and fails only here. 9779 is the sdist's own byte # count, it is what this build's .deb carries, and the two were compared with # `cmp'. check_eq $P/module-size 9779 \ "$(wc -c < "$MOD" 2>/dev/null | tr -d ' ')" # BST-REDIRECT-OK: forked -- inside $( ) # The byte-compiled copy, which is why `import' on this target is a read rather # than a parse of 10 KB of source. A single module's cache lands in # site-packages' OWN `__pycache__' -- not in a package directory's, which is # what a package would have -- and is pinned by path for that reason. check $P/pycache-present test -s "$SITE/__pycache__/vcversioner.cpython-312.pyc" # NO COMPILED EXTENSION SHIPS. On this image a `.so' in site-packages is not a # speed-up, it is an ImportError: python3 here is one static binary with an # empty lib-dynload (BRINTOS-1476). Asserted over the payload rather than over # the recipe. check_eq $P/no-so "" "$(find "$SITE" -maxdepth 1 -name 'vcversioner*.so' 2>/dev/null)" # NO EXECUTABLE. setup.py declares no `scripts' and its only entry point is a # distutils setup keyword, which is a library hook rather than a console # script, so nothing lands in /usr/bin. check $P/no-bin-script test ! -e /usr/bin/vcversioner # ------------------------------------------------------------- dist-info --- # The installed metadata, which is what `importlib.metadata' answers from and # the only carrier of the version -- the module itself has no `__version__' # (`version-attr' below asserts that absence, and it is a notable one in a # package whose whole subject is version numbers). check $P/dist-info-dir test -d "$DI" check $P/dist-info-record test -s "$DI/RECORD" check $P/dist-info-metadata test -s "$DI/METADATA" check $P/dist-info-wheel test -s "$DI/WHEEL" # THE WHOLE FILE SET, not just the files this suite happens to name. A release # that started shipping an extra metadata file would otherwise go unnoticed. # LC_ALL=C so the order is the byte order rather than the guest's locale. check_eq $P/dist-info-file-set \ "METADATA,RECORD,WHEEL,entry_points.txt,top_level.txt," \ "$(LC_ALL=C ls -1 "$DI" 2>/dev/null | LC_ALL=C sort | tr '\n' ',')" check_eq $P/dist-info-top-level "vcversioner" \ "$(cat "$DI/top_level.txt" 2>/dev/null | tr -d ' \r\n')" # NO LICENSE FILE SHIPS IN THE DIST-INFO, and that is not an oversight: the # sdist carries no COPYING or LICENSE at all, which is why the recipe's # LIC_FILES_CHKSUM checksums PKG-INFO's `License: ISC' line instead. This is # the runtime half of that -- asserted as an absence so the two halves cannot # drift apart silently. check $P/no-license-file test ! -e "$DI/LICENSE" check $P/metadata-license grep -q '^License: ISC$' "$DI/METADATA" check $P/metadata-version grep -q '^Version: 2\.16\.0\.0$' "$DI/METADATA" # THE ENTRY POINT IS A distutils SETUP KEYWORD. It is how `setup_requires' plus # `vcversioner={}' in a caller's setup.py reaches `vcversioner:setup', which is # the hook `setup-hook-version' below exercises directly. check $P/entry-point-group grep -q '^\[distutils\.setup_keywords\]$' "$DI/entry_points.txt" check $P/entry-point-line grep -q '^vcversioner = vcversioner:setup$' "$DI/entry_points.txt" # --------------------------------------------------------------- dpkg --- # The package is REGISTERED, not merely unpacked. A payload that arrived some # other way would pass every path case above and fail here. check $P/dpkg-list test -s "$PKGLIST" check $P/dpkg-lists-module grep -q "^$MOD\$" "$PKGLIST" # ------------------------------------------------------------------ import --- # ONE case for the bare import, so an import failure is legible from the # console without reading the probe's transcript. check $P/import python3 -c 'import vcversioner' # ------------------------------------------------------------------- probe --- if [ ! -s "$PROBE" ]; then bst_notok $P/probe-present "probe missing at $PROBE" exit 0 fi bst_ok $P/probe-present # 1500s, inside the 1800s suite budget so a hung probe is reported as a probe # overrun naming the last line it printed, rather than as a bare suite timeout. # # The probe EXITS 0 even when the import fails, so this case passing means "the # probe ran", not "the module works" -- `import-ok' below is what carries that. bst_probe $P/probe-run "$W/probe" 1500 python3 "$PROBE" OUT="$W/probe" # pv : the value the probe emitted for , or empty. `head -n 1' # because a key is emitted once and a duplicate would otherwise concatenate; # `cut -f2-' because a value may legitimately contain `='. pv() { grep "^$1=" "$OUT" 2>/dev/null | head -n 1 | cut -d= -f2-; } # The probe reached its end. Every case below reads a value it printed, so a # probe that stopped early would otherwise turn into a run of empty-string # mismatches that all name the wrong thing. check_eq $P/probe-complete "yes" "$(pv probe_done)" _imp=$(pv import_ok) check_eq $P/import-ok "yes" "$_imp" if [ "$_imp" != "yes" ]; then # The module did not import. Skip -- not red -- every behavioural case, each # with the reason attached: they are all one fact, and eighty reds would bury # it rather than report it. `import' and `import-ok' above are the two reds # that carry the verdict, and the design doc attributes them. _why="vcversioner did not import: $(pv import_error)" for _c in probe-count module-file module-name is-package public-names \ version-attr has-all has-find-version find-version-callable \ has-setup-hook has-version-type version-fields version-is-tuple \ print-prefix fix-path sig-params default-root \ default-version-file default-strip-prefix default-include-dev \ default-vcs-args vcs-table-paths vcs-table-git vcs-table-hg \ file-exit file-version file-commits file-sha file-popen-unused \ file-content-after version-index-matches version-is-namedtuple \ version-len zero-commits-version strip-v-version \ strip-custom-version strip-default-version no-dev-version \ hg-commits hg-version hg-nodec-commits hg-nodec-version \ git-nodec-version vcs-exit vcs-version vcs-commits vcs-sha \ vcs-file-exists vcs-file-content vcs-file-bytes \ vcs-nofile-version vcs-nofile-no-file vcs-overwrite-version \ vcs-overwrite-file root-subst-version root-subst-sha \ explicit-root-version explicit-root-cwd-clean modpath-written \ modpath-second-written modpath-content modpath-bytes \ modpath-exec-version modpath-exec-sha modpath-exec-revision \ modpath-none-by-default nothing-exit nothing-result \ nothing-msg-prefix nothing-msg-novcs nothing-msg-notpresent \ nothing-msg-tarball nothing-wrote-nothing nothing-nofile-exit \ nothing-nofile-msg-novcs nothing-nofile-no-tarball \ rc-ignored-exit rc-ignored-version rc-ignored-file \ rc-fallback-version rc-nofile-exit rc-nofile-msg-failed \ rc-nofile-msg-banner rc-nofile-msg-stderr missing-prog-version \ missing-prog-nofile-exit missing-prog-nofile-msg \ unparsable-exit unparsable-msg unparsable-no-file \ two-parts-exit four-parts-version four-parts-sha \ empty-file-exit empty-file-msg-parse git-dir-exit \ git-dir-msg-failed git-dir-fallback-version git-args-version \ git-args-warned git-args-warning-class git-args-warning-text \ setup-hook-version setup-hook-is-str; do bst_skip $P/$_c "$_why" done rm -rf "$W" exit 0 fi # The probe emitted every key it intended to. A probe that returned 0 having # skipped a section would otherwise leave those cases reading empty strings. check_eq $P/probe-count "106" "$(pv emitted_count)" # --------------------------------------------------------- module surface --- # WHERE THE IMPORT ACTUALLY CAME FROM. Pinned to the site-packages path so an # import satisfied by a stray copy on `sys.path' -- a leftover in the cwd, a # second install -- is a red here rather than a green suite testing the wrong # file. check_eq $P/module-file "$MOD" "$(pv module_file)" check_eq $P/module-name "vcversioner" "$(pv module_name)" check_eq $P/is-package "no" "$(pv is_package)" # THE WHOLE NON-UNDERSCORE SURFACE, in one string. It includes the stdlib names # the module imports at top level and the two `__future__' features it enables, # because `dir()' is what a caller sees and hiding four of those names would # make this case agree with a module that had lost `find_version'. check_eq $P/public-names \ "Version,collections,find_version,os,print,print_function,setup,subprocess,unicode_literals,warnings" \ "$(pv public_names)" check_eq $P/has-find-version "yes" "$(pv has_find_version)" check_eq $P/find-version-callable "yes" "$(pv find_version_callable)" check_eq $P/has-setup-hook "yes" "$(pv has_setup_hook)" check_eq $P/has-version-type "yes" "$(pv has_Version)" # NO `__version__' AND NO `__all__'. Both asserted as ABSENCES because both are # what a reader assumes is there, and the first is the joke of the package: the # module that computes versions carries none of its own. It lives only in the # dist-info (`metadata-version' above). check_eq $P/version-attr "no" "$(pv has_version_attr)" check_eq $P/has-all "no" "$(pv has_all)" # THE NAMED TUPLE THE TICKET ASKS ABOUT, described by its declaration. The # field/index agreement is measured separately, on a real return value, by # `version-index-matches' below -- a `_fields' that drifted from the element # order would satisfy this case alone. check_eq $P/version-fields "version,commits,sha" "$(pv Version_fields)" check_eq $P/version-is-tuple "yes" "$(pv Version_is_tuple)" # THE MODULE SHADOWS `print'. Every diagnostic it emits is prefixed # `vcversioner:' by a module-level function of that name, which is what the # failure-path message cases below are matching against. check_eq $P/print-prefix "vcversioner: hello" "$(pv print_prefix)" # `/' is translated to the platform separator. On this arm os.sep IS `/', so # the assertion is that the translation is a no-op HERE, not that it does # nothing anywhere. check_eq $P/fix-path "a/b/c" "$(pv fix_path)" # ---------------------------------------------------- signature and defaults --- # The defaults are the package's documented contract and every behavioural case # below is written against them, so they are asserted rather than assumed. The # parameter list is asserted in ORDER, because two of these are positional in # every caller that predates the keyword-only era. check_eq $P/sig-params \ "include_dev_version,root,version_file,version_module_paths,git_args,vcs_args,decrement_dev_version,strip_prefix,Popen,open" \ "$(pv sig_params)" check_eq $P/default-root '%(pwd)s' "$(pv default_root)" check_eq $P/default-version-file '%(root)s/version.txt' "$(pv default_version_file)" check_eq $P/default-strip-prefix "v" "$(pv default_strip_prefix)" check_eq $P/default-include-dev "True" "$(pv default_include_dev)" check_eq $P/default-vcs-args "None" "$(pv default_vcs_args)" # THE AUTO-DETECTION TABLE, asserted as data. The suite cannot exercise the git # or hg branch for real without a repository, so the table itself is the # evidence for what `vcs_args=None' would run -- and it is the table # `git-dir-exit' below depends on having selected. check_eq $P/vcs-table-paths '%(root)s/.git,%(root)s/.hg' "$(pv vcs_table_paths)" check_eq $P/vcs-table-git \ 'git --git-dir %(root)s/.git describe --tags --long' "$(pv vcs_table_git_args)" check_eq $P/vcs-table-hg \ 'hg log -R %(root)s -r . --template {latesttag}-{latesttagdistance}-hg{node|short}' \ "$(pv vcs_table_hg_args)" # ================== link 2 of the chain: version.txt, no VCS ================== # THE CASE THE README IS ABOUT: an sdist unpacked away from its checkout. No # `.git' and no `.hg' in the root, so auto-detection finds nothing, `vcs_args' # stays None and NO SUBPROCESS IS SPAWNED AT ALL. `file-popen-unused' is the # witness for the VCS being left alone, through the module's own documented # `Popen=' testing hook: the probe passes a sentinel that would record its own # call, and the assertion is that it never was. check_eq $P/file-exit "None" "$(pv file_exit)" check_eq $P/file-version "1.2.3.post4" "$(pv file_version)" check_eq $P/file-commits "4" "$(pv file_commits)" check_eq $P/file-sha "gabcdef" "$(pv file_sha)" check_eq $P/file-popen-unused "no" "$(pv file_popen_called)" # THE FILE IS REWRITTEN WITH WHAT WAS READ, so its content is unchanged. That # the write happens on this path at all is not obvious from the docs and is # what makes `unparsable-no-file' below worth its line. check_eq $P/file-content-after "1.2.3-4-gabcdef" "$(pv file_content_after)" # THE RETURN VALUE IS THE NAMED TUPLE, and its fields and indices agree. check_eq $P/version-index-matches "yes" "$(pv version_index_matches)" check_eq $P/version-is-namedtuple "yes" "$(pv version_is_Version)" check_eq $P/version-len "3" "$(pv version_len)" # `commits' of 0 means the tag itself, with no `.post' suffix -- the difference # between a release build and a build off a later commit, and the branch # `file-version' above does not take. check_eq $P/zero-commits-version "1.2.3" "$(pv zero_commits_version)" # THE DEFAULT `strip_prefix' IS `v', so a v-prefixed tag loses it... check_eq $P/strip-v-version "1.2.3" "$(pv strip_v_version)" # ...a custom prefix is honoured instead, which is the git-dch case the # docstring names... check_eq $P/strip-custom-version "1.5" "$(pv strip_custom_version)" # ...and the SAME tag under the default prefix keeps the whole of itself. This # is what makes the case above evidence rather than a coincidence: the strip is # a prefix test, not a path split. check_eq $P/strip-default-version "debian/1.5" "$(pv strip_default_version)" # `include_dev_version=False' suppresses the `.post' suffix even with commits # after the tag -- the same input as `file-version', one keyword apart. check_eq $P/no-dev-version "1.2.3" "$(pv no_dev_version)" # AN hg REVISION DECREMENTS BY DEFAULT. A sha beginning `hg' sets # `decrement_dev_version' when the caller did not, because hg needs a commit to # make a tag. The adjusted COUNT is asserted beside the version string, because # the count is where the arithmetic shows. check_eq $P/hg-commits "2" "$(pv hg_commits)" check_eq $P/hg-version "1.0.post2" "$(pv hg_version)" # ...and the same input with the flag forced off keeps the raw count, which is # what says the decrement is the flag's doing rather than the parse's. check_eq $P/hg-nodec-commits "3" "$(pv hg_nodec_commits)" check_eq $P/hg-nodec-version "1.0.post3" "$(pv hg_nodec_version)" # A GIT sha DOES NOT DECREMENT: the trigger is the literal `hg' prefix, so the # ordinary case must be shown not to take that branch. check_eq $P/git-nodec-version "1.0.post3" "$(pv git_nodec_version)" # ================== link 1 of the chain: a VCS command runs ================== # No version.txt, and a command that prints what `git describe --tags --long' # would. The version is derived from that output AND WRITTEN INTO version.txt, # which is the behaviour the whole package exists for -- so the file's contents # are asserted LITERALLY, and its byte count separately, not merely its # presence. check_eq $P/vcs-exit "None" "$(pv vcs_exit)" check_eq $P/vcs-version "2.0.post5" "$(pv vcs_version)" check_eq $P/vcs-commits "5" "$(pv vcs_commits)" check_eq $P/vcs-sha "gcafe" "$(pv vcs_sha)" check_eq $P/vcs-file-exists "yes" "$(pv vcs_file_exists)" # RAW, and with no trailing newline: the module strips the VCS output before # writing it. A file that kept the newline would still parse, so only the # literal comparison and the byte count catch it. check_eq $P/vcs-file-content "2.0-5-gcafe" "$(pv vcs_file_content)" check_eq $P/vcs-file-bytes "11" "$(pv vcs_file_bytes)" # `version_file=None' DISABLES THE FILE ENTIRELY. The version still comes from # the command and nothing is written -- the documented way to use the package # in a tree that must stay clean. check_eq $P/vcs-nofile-version "2.0.post5" "$(pv vcs_nofile_version)" check_eq $P/vcs-nofile-no-file "yes" "$(pv vcs_nofile_no_file)" # THE VCS WINS OVER AN EXISTING FILE, AND OVERWRITES IT. version.txt is a # fallback, not a cache: a stale file beside a working checkout must not pin # the version. Both the returned version and the file's new contents are # asserted, because either alone is consistent with the other having been left # as it was. check_eq $P/vcs-overwrite-version "3.0.post1" "$(pv vcs_overwrite_version)" check_eq $P/vcs-overwrite-file "3.0-1-gnew" "$(pv vcs_overwrite_file)" # `%(root)s' IS SUBSTITUTED INTO EVERY vcs_args ELEMENT. The docstring promises # it and the built-in git invocation depends on it, so it is measured with a # command that echoes back the basename of the argument it was handed: # `%(root)s/marker' arriving substituted is what makes the sha `gmarker'. check_eq $P/root-subst-version "9.0" "$(pv root_subst_version)" check_eq $P/root-subst-sha "gmarker" "$(pv root_subst_sha)" # AN EXPLICIT `root' MOVES THE VERSION FILE WITH IT. The call is made from a # DIFFERENT directory, so a version_file resolved against the cwd instead of # against root would find nothing and exit 2 -- and the cwd is asserted to have # been left clean, which is the other half of the same claim. check_eq $P/explicit-root-version "4.4" "$(pv explicit_root_version)" check_eq $P/explicit-root-cwd-clean "yes" "$(pv explicit_root_cwd_clean)" # ------------------------------------------------------- version modules --- # `version_module_paths' WRITES THE MODULE IT PROMISES. The contents are # compared LITERALLY -- newlines shown as `|' so the value stays one counted # result -- because the file is generated source that a caller's `__init__.py' # does `from ._version import __version__, __sha__' against. What it defines is # the contract, and a change in it is a change to every such caller. # # Note `__sha__' and `__revision__' are the SAME value: the module writes the # sha twice under two names, and both are asserted so that a release which made # them differ is noticed. check_eq $P/modpath-written "yes" "$(pv modpath_written)" check_eq $P/modpath-second-written "yes" "$(pv modpath_second_written)" check_eq $P/modpath-content \ "|# This file is automatically generated by setup.py.|__version__ = '1.2.3.post4'|__sha__ = 'gabcdef'|__revision__ = 'gabcdef'|" \ "$(pv modpath_content)" check_eq $P/modpath-bytes "126" "$(pv modpath_bytes)" # ...and it is valid python defining what it claims. The probe execs the file # it was handed rather than importing it, so this is the generated source being # read back, not a second computation of the same values. check_eq $P/modpath-exec-version "1.2.3.post4" "$(pv modpath_exec_version)" check_eq $P/modpath-exec-sha "gabcdef" "$(pv modpath_exec_sha)" check_eq $P/modpath-exec-revision "gabcdef" "$(pv modpath_exec_revision)" # NO MODULE IS WRITTEN when none is asked for. The default is an empty tuple, # so a release that started writing one unbidden would drop a file into every # caller's source tree. check_eq $P/modpath-none-by-default "yes" "$(pv modpath_none_by_default)" # ====================== link 3 of the chain: hard failure ====================== # The contract on failure is `SystemExit(2)' with a printed explanation, NEVER # a silent `0.0.0' that would compare wrongly against every real release for # the rest of the package's life. Each shape below asserts the exit code; the # messages that do not embed a temporary path are asserted literally, and the # ones that do are asserted by their parts. # NEITHER a VCS nor a version.txt -- the github-tarball case the module prints # advice for. `nothing-result' says the call did not return a version at all, # and `nothing-wrote-nothing' says it left the directory empty rather than # caching a failure for the next run to read back as a success. check_eq $P/nothing-exit "2" "$(pv nothing_exit)" check_eq $P/nothing-result "None" "$(pv nothing_result)" check_eq $P/nothing-msg-prefix "yes" "$(pv nothing_msg_prefix)" check_eq $P/nothing-msg-novcs "yes" "$(pv nothing_msg_novcs)" check_eq $P/nothing-msg-notpresent "yes" "$(pv nothing_msg_notpresent)" check_eq $P/nothing-msg-tarball "yes" "$(pv nothing_msg_tarball)" check_eq $P/nothing-wrote-nothing "yes" "$(pv nothing_wrote_nothing)" # NEITHER, with the version file DISABLED. A different branch with a different # message -- one line, and no tarball advice, because there is no file for the # advice to be about. Asserted so the two branches cannot collapse into one. check_eq $P/nothing-nofile-exit "2" "$(pv nothing_nofile_exit)" check_eq $P/nothing-nofile-msg-novcs "yes" "$(pv nothing_nofile_msg_novcs)" check_eq $P/nothing-nofile-no-tarball "yes" "$(pv nothing_nofile_no_tarball)" # A VCS COMMAND THAT EXITS NON-ZERO BUT PRINTS A USABLE VERSION IS BELIEVED -- # see the header. The status is read nowhere; emptiness of stdout is the only # failure signal. Asserted so that a release which STARTED checking the status # is noticed, because it would change what every caller with a noisy VCS # wrapper gets. The file is written too, which is what makes the belief stick. check_eq $P/rc-ignored-exit "None" "$(pv rc_ignored_exit)" check_eq $P/rc-ignored-version "9.9.post2" "$(pv rc_ignored_version)" check_eq $P/rc-ignored-file "9.9-2-gbad" "$(pv rc_ignored_file)" # ...and the other half: a command that fails SILENTLY falls back to # version.txt. Empty stdout is what "failed" means here. check_eq $P/rc-fallback-version "7.7" "$(pv rc_fallback_version)" # With no file to fall back on, that same command is a hard exit -- and the # message names the command that failed, then dumps its stderr under a banner. # That banner is the ONLY place a caller sees why the VCS was unhappy, so its # presence and the stderr line inside it are both asserted. check_eq $P/rc-nofile-exit "2" "$(pv rc_nofile_exit)" check_eq $P/rc-nofile-msg-failed "yes" "$(pv rc_nofile_msg_failed)" check_eq $P/rc-nofile-msg-banner "yes" "$(pv rc_nofile_msg_banner)" check_eq $P/rc-nofile-msg-stderr "yes" "$(pv rc_nofile_msg_stderr)" # A VCS PROGRAM THAT IS NOT INSTALLED. Popen raises OSError, which is caught # and treated exactly as a failure -- so the version.txt fallback still works # on an image with no git, which is what makes a stale `.git' in a source # tarball harmless. Both branches: with a file, and without one. check_eq $P/missing-prog-version "5.5" "$(pv missing_prog_version)" check_eq $P/missing-prog-nofile-exit "2" "$(pv missing_prog_nofile_exit)" check_eq $P/missing-prog-nofile-msg "yes" "$(pv missing_prog_nofile_msg)" # AN UNPARSABLE STRING. `rsplit('-', 2)' needs three parts and `garbage' has # one. The message is asserted LITERALLY because `version_source' is the fixed # string `VCS' on this path, so no temporary path appears in it. check_eq $P/unparsable-exit "2" "$(pv unparsable_exit)" check_eq $P/unparsable-msg \ "vcversioner: 'garbage' (from VCS) couldn't be parsed into a version." \ "$(pv unparsable_msg)" # NOTHING IS CACHED. The file write happens after the parse, so a version that # could not be parsed must not be sitting in version.txt for the next run. check_eq $P/unparsable-no-file "yes" "$(pv unparsable_no_file)" # TWO parts is still one too few. This boundary matters because `1.0-3' is the # shape a hand-written version.txt is most likely to take. check_eq $P/two-parts-exit "2" "$(pv two_parts_exit)" # FOUR parts PARSES, because the split is from the RIGHT: `my-tag-1-gaaa' is # the tag `my-tag' with one commit after it. A left split would take `my' as # the tag, which is the bug this case exists to catch -- and it is a real # shape, since hyphenated tags are ordinary. check_eq $P/four-parts-version "my-tag.post1" "$(pv four_parts_version)" check_eq $P/four-parts-sha "gaaa" "$(pv four_parts_sha)" # AN EMPTY version.txt IS NOT A VERSION. It EXISTS, so the "isn't present" # branch is not taken; it reads back as the empty string, which then fails the # parse. The message case is what distinguishes those two branches. check_eq $P/empty-file-exit "2" "$(pv empty_file_exit)" check_eq $P/empty-file-msg-parse "yes" "$(pv empty_file_msg_parse)" # ------------------------------------------------------------ auto-detection --- # An empty `.git' DIRECTORY selects the built-in git command, which then fails # -- whether git is on this image (it errors: not a repository) or absent # (Popen raises OSError, caught). Either way there is no version.txt to fall # back on and the answer is exit 2, which is why this case is stable on an # image whose git status this suite does not assume. check_eq $P/git-dir-exit "2" "$(pv git_dir_exit)" check_eq $P/git-dir-msg-failed "yes" "$(pv git_dir_msg_failed)" # ...and with a version.txt beside that `.git', the same detection falls # through to the file rather than exiting. This is the sdist-with-stale-.git # case, and it is the one that says detection failing is not fatal. check_eq $P/git-dir-fallback-version "6.6" "$(pv git_dir_fallback_version)" # --------------------------------------------------- the deprecated alias --- # `git_args' still works and still warns. Asserted because the warning is the # only notice a caller gets, and because a suite that exercised only `vcs_args' # would let a release drop the alias without anything going red. check_eq $P/git-args-version "8.8" "$(pv git_args_version)" check_eq $P/git-args-warned "yes" "$(pv git_args_warned)" check_eq $P/git-args-warning-class "DeprecationWarning" "$(pv git_args_warning_class)" check_eq $P/git-args-warning-text \ 'passing `git_args is deprecated; please use vcs_args' \ "$(pv git_args_warning_text)" # ------------------------------------------------------------- setup() hook --- # The distutils keyword hook the entry point wires up: `setup(dist, attr, # value)' calls find_version with as keyword arguments and assigns the # result to `dist.metadata.version'. The METADATA being written is what is # asserted, not merely that the function exists -- and that it is the version # STRING, the commits and sha having been dropped, which is why a caller that # wants them calls find_version directly. check_eq $P/setup-hook-version "6.6.post2" "$(pv setup_hook_version)" check_eq $P/setup-hook-is-str "yes" "$(pv setup_hook_is_str)" rm -rf "$W"