diff --git a/tests/blacklist-treedirstate b/tests/blacklist-treedirstate new file mode 100644 --- /dev/null +++ b/tests/blacklist-treedirstate @@ -0,0 +1,46 @@ +# These tests do not work with treedirstate overriding the dirstate +# implementation. +# +# To run the core hg tests with treedirstate, use: +# python run-tests.py \ +# --extra-config extensions.treedirstate= \ +# --extra-config treedirstate.useinnewrepos=True \ +# --blacklist + +# These tests do not support the existence of an extra extension or new entries +# in repo requirements. +test-basic.t +test-blackbox.t +test-command-template.t +test-commandserver.t +test-completion.t +test-debugextensions.t +test-devel-warnings.t +test-extension.t +test-fncache.t +test-globalopts.t +test-hardlinks.t +test-help.t +test-hgrc.t +test-inherit-mode.t +test-init.t +test-keyword.t +test-lfconvert.t +test-repo-compengines.t +test-revlog-v2.t +test-run-tests.t +test-sparse-requirement.t +test-static-http.t +test-upgrade-repo.t +test-wireproto.t + +# These tests monkey-patch or modify internal state of the core dirstate +# implementation. +test-merge-tools.t +test-merge1.t +test-revert.t +test-subrepo.t +test-contrib-perf.t + +# These tests rely on specific behaviour of the core dirstate implementation. +test-dirstate-backup.t diff --git a/tests/check-ext.py b/tests/check-ext.py --- a/tests/check-ext.py +++ b/tests/check-ext.py @@ -17,6 +17,9 @@ foreignexts = set(['remotenames']) foreignextre = re.compile(r'(%s)' % '|'.join(foreignexts)) +# pattern for rust extensions +rustextre = re.compile(r'^hgext3rd\.rust\..*') + # extensions in this repo repoexts = set(os.path.basename(p).split('.')[0] for p in glob('hgext3rd/*.py') if '__' not in p) @@ -58,6 +61,8 @@ requiredexts = set(m.group(1).split()) unknownexts = requiredexts - foreignexts for e in unknownexts: + if rustextre.match(e): + continue if e in repoexts: msg = 'do not require non-foreign extension %s' else: diff --git a/tests/test-treedirstate-backup.t b/tests/test-treedirstate-backup.t new file mode 100644 --- /dev/null +++ b/tests/test-treedirstate-backup.t @@ -0,0 +1,30 @@ + $ . $TESTDIR/require-ext.sh hgext3rd.rust.treedirstate + +Copy of test-dirstate-backup.t for treedirstate + + $ cat >> $HGRCPATH < [extensions] + > treedirstate=$TESTDIR/../hgext3rd/treedirstate.py + > [treedirstate] + > useinnewrepos=True + > EOF + +Set up + + $ hg init repo + $ cd repo + +Try to import an empty patch + + $ hg import --no-commit - < EOF + applying patch from stdin + abort: stdin: no diffs found + [255] + +No dirstate backups are left behind + + $ ls .hg/dirstate* | sort + .hg/dirstate + .hg/dirstate.tree.* (glob) + diff --git a/tests/test-treedirstate-core.t b/tests/test-treedirstate-core.t new file mode 100644 --- /dev/null +++ b/tests/test-treedirstate-core.t @@ -0,0 +1,104 @@ + $ . $TESTDIR/require-ext.sh hgext3rd.rust.treedirstate + +Copy of test-dirstate.t for treedirstate + + $ cat >> $HGRCPATH < [extensions] + > treedirstate=$TESTDIR/../hgext3rd/treedirstate.py + > [treedirstate] + > useinnewrepos=True + > EOF + +------ Test dirstate._dirs refcounting + + $ hg init t + $ cd t + $ mkdir -p a/b/c/d + $ touch a/b/c/d/x + $ touch a/b/c/d/y + $ touch a/b/c/d/z + $ hg ci -Am m + adding a/b/c/d/x + adding a/b/c/d/y + adding a/b/c/d/z + $ hg mv a z + moving a/b/c/d/x to z/b/c/d/x (glob) + moving a/b/c/d/y to z/b/c/d/y (glob) + moving a/b/c/d/z to z/b/c/d/z (glob) + +Test name collisions + + $ rm z/b/c/d/x + $ mkdir z/b/c/d/x + $ touch z/b/c/d/x/y + $ hg add z/b/c/d/x/y + abort: file 'z/b/c/d/x' in dirstate clashes with 'z/b/c/d/x/y' + [255] + $ rm -rf z/b/c/d + $ touch z/b/c/d + $ hg add z/b/c/d + abort: directory 'z/b/c/d' already in dirstate + [255] + + $ cd .. + +Issue1790: dirstate entry locked into unset if file mtime is set into +the future + +Prepare test repo: + + $ hg init u + $ cd u + $ echo a > a + $ hg add + adding a + $ hg ci -m1 + +Set mtime of a into the future: + + $ touch -t 202101011200 a + +Status must not set a's entry to unset (issue1790): + + $ hg status + $ hg debugstate + n 644 2 2021-01-01 12:00:00 a + +Test modulo storage/comparison of absurd dates: + +#if no-aix + $ touch -t 195001011200 a + $ hg st + $ hg debugstate + n 644 2 2018-01-19 15:14:08 a +#endif + +Verify that exceptions during a dirstate change leave the dirstate +coherent (issue4353) + + $ cat > ../dirstateexception.py < from __future__ import absolute_import + > from mercurial import ( + > error, + > extensions, + > merge, + > ) + > + > def wraprecordupdates(orig, repo, actions, branchmerge): + > raise error.Abort("simulated error while recording dirstateupdates") + > + > def reposetup(ui, repo): + > extensions.wrapfunction(merge, 'recordupdates', wraprecordupdates) + > EOF + + $ hg rm a + $ hg commit -m 'rm a' + $ echo "[extensions]" >> .hg/hgrc + $ echo "dirstateex=../dirstateexception.py" >> .hg/hgrc + $ hg up 0 + abort: simulated error while recording dirstateupdates + [255] + $ hg log -r . -T '{rev}\n' + 1 + $ hg status + ? a diff --git a/tests/test-treedirstate-nonnormalset.t b/tests/test-treedirstate-nonnormalset.t new file mode 100644 --- /dev/null +++ b/tests/test-treedirstate-nonnormalset.t @@ -0,0 +1,33 @@ + $ . $TESTDIR/require-ext.sh hgext3rd.rust.treedirstate + +Copy of test-dirstate-nonnormalsets.t for treedirstate + + $ cat >> $HGRCPATH < [extensions] + > treedirstate=$TESTDIR/../hgext3rd/treedirstate.py + > [treedirstate] + > useinnewrepos=True + > EOF + + $ cat >> $HGRCPATH << EOF + > [ui] + > logtemplate="{rev}:{node|short} ({phase}) [{tags} {bookmarks}] {desc|firstline}\n" + > [extensions] + > dirstateparanoidcheck = $RUNTESTDIR/../contrib/dirstatenonnormalcheck.py + > [experimental] + > nonnormalparanoidcheck = True + > [devel] + > all-warnings=True + > EOF + $ mkcommit() { + > echo "$1" > "$1" + > hg add "$1" + > hg ci -m "add $1" + > } + + $ hg init testrepo + $ cd testrepo + $ mkcommit a + $ mkcommit b + $ mkcommit c + $ hg status diff --git a/tests/test-treedirstate-race.t b/tests/test-treedirstate-race.t new file mode 100644 --- /dev/null +++ b/tests/test-treedirstate-race.t @@ -0,0 +1,249 @@ + $ . $TESTDIR/require-ext.sh hgext3rd.rust.treedirstate + +Copy of core Mercurial test-dirstate-race, for treedirstate. + + $ cat >> $HGRCPATH < [extensions] + > treedirstate=$TESTDIR/../hgext3rd/treedirstate.py + > [treedirstate] + > useinnewrepos=True + > EOF + +Setup + + $ hg init repo + $ cd repo + $ echo a > a + $ hg add a + $ hg commit -m test + +Do we ever miss a sub-second change?: + + $ for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20; do + > hg co -qC 0 + > echo b > a + > hg st + > done + M a + M a + M a + M a + M a + M a + M a + M a + M a + M a + M a + M a + M a + M a + M a + M a + M a + M a + M a + M a + + $ echo test > b + $ mkdir dir1 + $ echo test > dir1/c + $ echo test > d + + $ echo test > e +#if execbit +A directory will typically have the execute bit -- make sure it doesn't get +confused with a file with the exec bit set + $ chmod +x e +#endif + + $ hg add b dir1 d e + adding dir1/c (glob) + $ hg commit -m test2 + + $ cat >> $TESTTMP/dirstaterace.py << EOF + > from mercurial import ( + > context, + > extensions, + > ) + > def extsetup(): + > extensions.wrapfunction(context.workingctx, '_checklookup', overridechecklookup) + > def overridechecklookup(orig, self, files): + > # make an update that changes the dirstate from underneath + > self._repo.ui.system(r"sh '$TESTTMP/dirstaterace.sh'", + > cwd=self._repo.root) + > return orig(self, files) + > EOF + + $ hg debugrebuilddirstate + $ hg debugdirstate + n 0 -1 unset a + n 0 -1 unset b + n 0 -1 unset d + n 0 -1 unset dir1/c + n 0 -1 unset e + +XXX Note that this returns M for files that got replaced by directories. This is +definitely a bug, but the fix for that is hard and the next status run is fine +anyway. + + $ cat > $TESTTMP/dirstaterace.sh < rm b && rm -r dir1 && rm d && mkdir d && rm e && mkdir e + > EOF + + $ hg status --config extensions.dirstaterace=$TESTTMP/dirstaterace.py + M d + M e + ! b + ! dir1/c + $ hg debugdirstate + n 644 2 * a (glob) + n 0 -1 unset b + n 0 -1 unset d + n 0 -1 unset dir1/c + n 0 -1 unset e + + $ hg status + ! b + ! d + ! dir1/c + ! e + + $ rmdir d e + $ hg update -C -q . + +Test that dirstate changes aren't written out at the end of "hg +status", if .hg/dirstate is already changed simultaneously before +acquisition of wlock in workingctx._poststatusfixup(). + +This avoidance is important to keep consistency of dirstate in race +condition (see issue5584 for detail). + + $ hg parents -q + 1:* (glob) + + $ hg debugrebuilddirstate + $ hg debugdirstate + n 0 -1 unset a + n 0 -1 unset b + n 0 -1 unset d + n 0 -1 unset dir1/c + n 0 -1 unset e + + $ cat > $TESTTMP/dirstaterace.sh < # This script assumes timetable of typical issue5584 case below: + > # + > # 1. "hg status" loads .hg/dirstate + > # 2. "hg status" confirms clean-ness of FILE + > # 3. "hg update -C 0" updates the working directory simultaneously + > # (FILE is removed, and FILE is dropped from .hg/dirstate) + > # 4. "hg status" acquires wlock + > # (.hg/dirstate is re-loaded = no FILE entry in dirstate) + > # 5. "hg status" marks FILE in dirstate as clean + > # (FILE entry is added to in-memory dirstate) + > # 6. "hg status" writes dirstate changes into .hg/dirstate + > # (FILE entry is written into .hg/dirstate) + > # + > # To reproduce similar situation easily and certainly, #2 and #3 + > # are swapped. "hg cat" below ensures #2 on "hg status" side. + > + > hg update -q -C 0 + > hg cat -r 1 b > b + > EOF + +"hg status" below should excludes "e", of which exec flag is set, for +portability of test scenario, because unsure but missing "e" is +treated differently in _checklookup() according to runtime platform. + +- "missing(!)" on POSIX, "pctx[f].cmp(self[f])" raises ENOENT +- "modified(M)" on Windows, "self.flags(f) != pctx.flags(f)" is True + + $ hg status --config extensions.dirstaterace=$TESTTMP/dirstaterace.py --debug -X path:e + skip updating dirstate: identity mismatch + M a + ! d + ! dir1/c + + $ hg parents -q + 0:* (glob) + $ hg files + a + $ hg debugdirstate + n * * * a (glob) + + $ rm b + +#if fsmonitor + +Create fsmonitor state. + + $ hg status + $ f --type .hg/fsmonitor.state + .hg/fsmonitor.state: file + +Test that invalidating fsmonitor state in the middle (which doesn't require the +wlock) causes the fsmonitor update to be skipped. +hg debugrebuilddirstate ensures that the dirstaterace hook will be called, but +it also invalidates the fsmonitor state. So back it up and restore it. + + $ mv .hg/fsmonitor.state .hg/fsmonitor.state.tmp + $ hg debugrebuilddirstate + $ mv .hg/fsmonitor.state.tmp .hg/fsmonitor.state + + $ cat > $TESTTMP/dirstaterace.sh < rm .hg/fsmonitor.state + > EOF + + $ hg status --config extensions.dirstaterace=$TESTTMP/dirstaterace.py --debug + skip updating fsmonitor.state: identity mismatch + $ f .hg/fsmonitor.state + .hg/fsmonitor.state: file not found + +#endif + +Set up a rebase situation for issue5581. + + $ echo c2 > a + $ echo c2 > b + $ hg add b + $ hg commit -m c2 + created new head + $ echo c3 >> a + $ hg commit -m c3 + $ hg update 2 + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ echo c4 >> a + $ echo c4 >> b + $ hg commit -m c4 + created new head + +Configure a merge tool that runs status in the middle of the rebase. The goal of +the status call is to trigger a potential bug if fsmonitor's state is written +even though the wlock is held by another process. The output of 'hg status' in +the merge tool goes to /dev/null because we're more interested in the results of +'hg status' run after the rebase. + + $ cat >> $TESTTMP/mergetool-race.sh << EOF + > echo "custom merge tool" + > printf "c2\nc3\nc4\n" > \$1 + > hg --cwd "$TESTTMP/repo" status > /dev/null + > echo "custom merge tool end" + > EOF + $ cat >> $HGRCPATH << EOF + > [extensions] + > rebase = + > [merge-tools] + > test.executable=sh + > test.args=$TESTTMP/mergetool-race.sh \$output + > EOF + + $ hg rebase -s . -d 3 --tool test + rebasing 4:b08445fd6b2a "c4" (tip) + merging a + custom merge tool + custom merge tool end + saved backup bundle to $TESTTMP/repo/.hg/strip-backup/* (glob) + +This hg status should be empty, whether or not fsmonitor is enabled (issue5581). + + $ hg status diff --git a/tests/test-treedirstate.t b/tests/test-treedirstate.t new file mode 100644 --- /dev/null +++ b/tests/test-treedirstate.t @@ -0,0 +1,202 @@ + $ . $TESTDIR/require-ext.sh hgext3rd.rust.treedirstate + +Setup + + $ cat >> $HGRCPATH < [extensions] + > treedirstate=$TESTDIR/../hgext3rd/treedirstate.py + > [treedirstate] + > useinnewrepos=True + > EOF + + $ hg init repo + $ cd repo + $ echo base > base + $ hg add base + $ hg debugdirstate + a 0 -1 unset base + $ hg commit -m "base" + $ hg debugdirstate + n 644 5 * base (glob) + +Create path-conflicting dirstates + + $ hg up -q 0 + $ echo a > a + $ hg add a + $ hg commit -m a + $ hg bookmark a + $ hg up -q 0 + $ mkdir a + $ echo a/a > a/a + $ hg add a/a + $ hg commit -m a/a + created new head + $ hg bookmark a/a + $ hg up -q a + $ hg status + $ hg rm a + $ hg status + R a + $ hg merge --force a/a + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ hg status + M a/a + R a + $ hg rm --force a/a + $ hg status + R a + R a/a + $ hg up -Cq 0 + +Attempt to create a path conflict in the manifest + + $ echo b > b + $ hg add b + $ hg commit -m b + created new head + $ rm b + $ mkdir b + $ echo b/b > b/b + $ hg add b/b + abort: file 'b' in dirstate clashes with 'b/b' + [255] + $ rm -rf b + $ hg up -Cq 0 + +Test warning when creating files that might give a casefold collision + +#if no-icasefs + $ echo data > FiLeNaMe + $ hg add FiLeNaMe + $ echo data > FILENAME + $ hg add FILENAME + warning: possible case-folding collision for FILENAME + $ rm -f FiLeNaMe FILENAME + $ hg up -Cq 0 +#endif + +Test dirfoldmap and filefoldmap on case insensitive filesystems + +#if icasefs + $ mkdir -p dirA/dirB/dirC + $ echo file1 > dira/File1 + $ echo file2 > dira/dirb/FILE2 + $ echo file3 > dira/dirb/dirc/FiLe3 + $ echo file4 > dira/dirb/dirc/file4 + $ hg add DIRA + adding dirA/File1 + adding dirA/dirB/FILE2 + adding dirA/dirB/dirC/FiLe3 + adding dirA/dirB/dirC/file4 + $ hg status + A dirA/File1 + A dirA/dirB/FILE2 + A dirA/dirB/dirC/FiLe3 + A dirA/dirB/dirC/file4 + $ hg forget dira/DIRB + removing dirA/dirB/FILE2 + removing dirA/dirB/dirC/FiLe3 + removing dirA/dirB/dirC/file4 + $ hg status + A dirA/File1 + ? dirA/dirB/FILE2 + ? dirA/dirB/dirC/FiLe3 + ? dirA/dirB/dirC/file4 + $ hg add dira/dirb/file2 + $ hg status + A dirA/File1 + A dirA/dirB/FILE2 + ? dirA/dirB/dirC/FiLe3 + ? dirA/dirB/dirC/file4 + $ rm -rf dirA +#endif + +Test autorepack + + $ ls .hg/dirstate.tree.* + .hg/dirstate.tree.* (glob) + $ echo data > file + +After the first repack, the old trees are kept around by the transaction undo backups. + $ hg add file --config treedirstate.minrepackthreshold=1 --config treedirstate.repackfactor=0 --debug | grep -v 'in use by' + adding file + auto-repacking treedirstate + +After the second repack, the tree is replaced by a new tree and then deleted. + $ hg forget file --config treedirstate.minrepackthreshold=1 --config treedirstate.repackfactor=0 --debug | grep -v 'in use by' + removing file + auto-repacking treedirstate + dirstate tree * unused, deleting (glob) + $ ls .hg/dirstate.tree.* + .hg/dirstate.tree.* (glob) + .hg/dirstate.tree.* (glob) + +Test downgrade and upgrade on pull + + $ for f in 1 2 3 4 5 ; do mkdir dir$f ; echo $f > dir$f/file$f ; hg add dir$f/file$f ; done + $ echo x > a + $ hg add a + $ hg commit -m "add files" + created new head + $ cd .. + $ hg clone repo clone + updating to branch default + 7 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ cd clone + $ hg merge 1 + merging a + warning: conflicts while merging a! (edit, then use 'hg resolve --mark') + 0 files updated, 0 files merged, 0 files removed, 1 files unresolved + use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon + [1] + $ echo data > newfile + $ hg add newfile + $ hg rm dir3/file3 + $ grep treedirstate .hg/requires + treedirstate + $ hg pull --config treedirstate.downgradeonpull=true + disabling treedirstate... + pulling from $TESTTMP/repo (glob) + searching for changes + no changes found + $ hg debugdirstate + m 0 -2 * a (glob) + n 644 5 * base (glob) + n 644 2 * dir1/file1 (glob) + n 644 2 * dir2/file2 (glob) + r 0 0 * dir3/file3 (glob) + n 644 2 * dir4/file4 (glob) + n 644 2 * dir5/file5 (glob) + a 0 -1 * newfile (glob) + $ grep treedirstate .hg/requires + [1] + $ hg pull --config treedirstate.upgradeonpull=true + migrating your repo to treedirstate which will make your hg commands faster... + pulling from $TESTTMP/repo (glob) + searching for changes + no changes found + $ hg debugdirstate + m 0 -2 * a (glob) + n 644 5 * base (glob) + n 644 2 * dir1/file1 (glob) + n 644 2 * dir2/file2 (glob) + r 0 0 * dir3/file3 (glob) + n 644 2 * dir4/file4 (glob) + n 644 2 * dir5/file5 (glob) + a 0 -1 * newfile (glob) + $ grep treedirstate .hg/requires + treedirstate + $ hg pull --config treedirstate.upgradeonpull=true + pulling from $TESTTMP/repo (glob) + searching for changes + no changes found + +Test cleaning up on normal commands + + $ echo fakedirstatetree > .hg/dirstate.tree.fake + $ hg forget newfile --debug --config treedirstate.cleanuppercent=100 | grep fake + dirstate tree fake unused, deleting + $ test -f .hg/dirstate.tree.fake + [1]