scmutil: add file object wrapper class to check ambiguity at closing
In Mercurial source tree, opening a file in "a"/"a+" mode like below
doesn't specify atomictemp=True for vfs, and this avoids file stat
ambiguity check by atomictempfile.
- writing changes out in revlog layer uses "a+" mode
- truncation in repair.strip() uses "a" mode
- truncation in transaction._playback() uses "a" mode
If steps below occurs at "the same time in sec", all of mtime, ctime
and size are same between (1) and (3).
1. append data to revlog-style file (and close transaction)
2. discard appended data by truncation (strip or rollback)
3. append same size but different data to revlog-style file again
Therefore, cache validation doesn't work after (3) as expected.
This patch adds file object wrapper class checkambigatclosing to check
(and get rid of) ambiguity at closing. It is used by vfs in subsequent
patch.
This is a part of ExactCacheValidationPlan.
https://www.mercurial-scm.org/wiki/ExactCacheValidationPlan
BTW, checkambigatclosing is tested in test-filecache.py, even though
it doesn't use filecache itself, because filecache assumes that file
stat ambiguity never occurs (and there is no another test-*.py related
to filecache).
#require test-repo
$ . "$TESTDIR/helpers-testrepo.sh"
$ import_checker="$TESTDIR"/../contrib/import-checker.py
Run the doctests from the import checker, and make sure
it's working correctly.
$ TERM=dumb
$ export TERM
$ python -m doctest $import_checker
Run additional tests for the import checker
$ mkdir testpackage
$ touch testpackage/__init__.py
$ cat > testpackage/multiple.py << EOF
> from __future__ import absolute_import
> import os, sys
> EOF
$ cat > testpackage/unsorted.py << EOF
> from __future__ import absolute_import
> import sys
> import os
> EOF
$ cat > testpackage/stdafterlocal.py << EOF
> from __future__ import absolute_import
> from . import unsorted
> import os
> EOF
$ cat > testpackage/requirerelative.py << EOF
> from __future__ import absolute_import
> import testpackage.unsorted
> EOF
$ cat > testpackage/importalias.py << EOF
> from __future__ import absolute_import
> import ui
> EOF
$ cat > testpackage/relativestdlib.py << EOF
> from __future__ import absolute_import
> from .. import os
> EOF
$ cat > testpackage/symbolimport.py << EOF
> from __future__ import absolute_import
> from .unsorted import foo
> EOF
$ cat > testpackage/latesymbolimport.py << EOF
> from __future__ import absolute_import
> from . import unsorted
> from mercurial.node import hex
> EOF
$ cat > testpackage/multiplegroups.py << EOF
> from __future__ import absolute_import
> from . import unsorted
> from . import more
> EOF
$ mkdir testpackage/subpackage
$ cat > testpackage/subpackage/levelpriority.py << EOF
> from __future__ import absolute_import
> from . import foo
> from .. import parent
> EOF
$ touch testpackage/subpackage/foo.py
$ cat > testpackage/subpackage/__init__.py << EOF
> from __future__ import absolute_import
> from . import levelpriority # should not cause cycle
> EOF
$ cat > testpackage/subpackage/localimport.py << EOF
> from __future__ import absolute_import
> from . import foo
> def bar():
> # should not cause "higher-level import should come first"
> from .. import unsorted
> # but other errors should be detected
> from .. import more
> import testpackage.subpackage.levelpriority
> EOF
$ cat > testpackage/importmodulefromsub.py << EOF
> from __future__ import absolute_import
> from .subpackage import foo # not a "direct symbol import"
> EOF
$ cat > testpackage/importsymbolfromsub.py << EOF
> from __future__ import absolute_import
> from .subpackage import foo, nonmodule
> EOF
$ cat > testpackage/sortedentries.py << EOF
> from __future__ import absolute_import
> from . import (
> foo,
> bar,
> )
> EOF
$ cat > testpackage/importfromalias.py << EOF
> from __future__ import absolute_import
> from . import ui
> EOF
$ cat > testpackage/importfromrelative.py << EOF
> from __future__ import absolute_import
> from testpackage.unsorted import foo
> EOF
$ mkdir testpackage2
$ touch testpackage2/__init__.py
$ cat > testpackage2/latesymbolimport.py << EOF
> from __future__ import absolute_import
> from testpackage import unsorted
> from mercurial.node import hex
> EOF
$ python "$import_checker" testpackage*/*.py testpackage/subpackage/*.py
testpackage/importalias.py:2: ui module must be "as" aliased to uimod
testpackage/importfromalias.py:2: ui from testpackage must be "as" aliased to uimod
testpackage/importfromrelative.py:2: import should be relative: testpackage.unsorted
testpackage/importfromrelative.py:2: direct symbol import foo from testpackage.unsorted
testpackage/importsymbolfromsub.py:2: direct symbol import nonmodule from testpackage.subpackage
testpackage/latesymbolimport.py:3: symbol import follows non-symbol import: mercurial.node
testpackage/multiple.py:2: multiple imported names: os, sys
testpackage/multiplegroups.py:3: multiple "from . import" statements
testpackage/relativestdlib.py:2: relative import of stdlib module
testpackage/requirerelative.py:2: import should be relative: testpackage.unsorted
testpackage/sortedentries.py:2: imports from testpackage not lexically sorted: bar < foo
testpackage/stdafterlocal.py:3: stdlib import "os" follows local import: testpackage
testpackage/subpackage/levelpriority.py:3: higher-level import should come first: testpackage
testpackage/subpackage/localimport.py:7: multiple "from .. import" statements
testpackage/subpackage/localimport.py:8: import should be relative: testpackage.subpackage.levelpriority
testpackage/symbolimport.py:2: direct symbol import foo from testpackage.unsorted
testpackage/unsorted.py:3: imports not lexically sorted: os < sys
testpackage2/latesymbolimport.py:3: symbol import follows non-symbol import: mercurial.node
[1]
$ cd "$TESTDIR"/..
There are a handful of cases here that require renaming a module so it
doesn't overlap with a stdlib module name. There are also some cycles
here that we should still endeavor to fix, and some cycles will be
hidden by deduplication algorithm in the cycle detector, so fixing
these may expose other cycles.
Known-bad files are excluded by -X as some of them would produce unstable
outputs, which should be fixed later.
$ hg locate 'set:**.py or grep(r"^#!.*?python")' \
> 'tests/**.t' \
> -X contrib/debugshell.py \
> -X contrib/win32/hgwebdir_wsgi.py \
> -X doc/gendoc.py \
> -X doc/hgmanpage.py \
> -X i18n/posplit \
> -X tests/test-hgweb-auth.py \
> -X tests/hypothesishelpers.py \
> -X tests/test-ctxmanager.py \
> -X tests/test-lock.py \
> -X tests/test-verify-repo-operations.py \
> -X tests/test-hook.t \
> -X tests/test-import.t \
> -X tests/test-check-module-imports.t \
> -X tests/test-commit-interactive.t \
> -X tests/test-contrib-check-code.t \
> -X tests/test-extension.t \
> -X tests/test-hghave.t \
> -X tests/test-hgweb-no-path-info.t \
> -X tests/test-hgweb-no-request-uri.t \
> -X tests/test-hgweb-non-interactive.t \
> | sed 's-\\-/-g' | python "$import_checker" -