diff mbox series

[v2,1/3] stash: add test to ensure reflog --rewrite --updatref behavior

Message ID 6e136b62ca4588cc58f2cb59b635eeaf14e6e20d.1645554652.git.gitgitgadget@gmail.com (mailing list archive)
State Superseded
Headers show
Series libify reflog | expand

Commit Message

John Cai Feb. 22, 2022, 6:30 p.m. UTC
From: John Cai <johncai86@gmail.com>

There is missing test coverage to ensure that the resulting reflogs
after a git stash drop has had its old oid rewritten if applicable, and
if the refs/stash has been updated if applicable.

Add two tests that verify both of these happen.

Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: John Cai <johncai86@gmail.com>
---
 t/t3903-stash.sh | 25 ++++++++++++++++++++++++-
 1 file changed, 24 insertions(+), 1 deletion(-)

Comments

Ævar Arnfjörð Bjarmason Feb. 23, 2022, 8:54 a.m. UTC | #1
On Tue, Feb 22 2022, John Cai via GitGitGadget wrote:

> From: John Cai <johncai86@gmail.com>
>
> There is missing test coverage to ensure that the resulting reflogs
> after a git stash drop has had its old oid rewritten if applicable, and
> if the refs/stash has been updated if applicable.

This test looks good, and if 3/3 is applied and either of the flags
you're passing is omitted they'll fail, so we know we have the missing
coverage here.

> Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
> Signed-off-by: John Cai <johncai86@gmail.com>
> ---
>  t/t3903-stash.sh | 25 ++++++++++++++++++++++++-
>  1 file changed, 24 insertions(+), 1 deletion(-)
>
> diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
> index b149e2af441..ec9cc5646d6 100755
> --- a/t/t3903-stash.sh
> +++ b/t/t3903-stash.sh
> @@ -185,10 +185,33 @@ test_expect_success 'drop middle stash by index' '
>  	test 1 = $(git show HEAD:file)
>  '
>  
> +test_expect_success 'drop stash reflog updates refs/stash' '
> +	git reset --hard &&
> +	git rev-parse refs/stash >expect &&
> +	echo 9 >file &&
> +	git stash &&
> +	git stash drop stash@{0} &&
> +	git rev-parse refs/stash >actual &&
> +	test_cmp expect actual
> +'

This one will be portable to the reftable backend.

> +test_expect_success 'drop stash reflog updates refs/stash with rewrite' '

But as I noted in <220222.86fsob88h7.gmgdl@evledraar.gmail.com> (but it
was easy to miss) this test will need to depend on REFFILES. So just
changing this line to:

    test_expect_success REFFILES 'drop stash[...]'

> +	git reset --hard &&
> +	echo 9 >file &&
> +	git stash &&
> +	oid="$(git rev-parse stash@{0})" &&
> +	git stash drop stash@{1} &&
> +	cut -d" " -f1-2 .git/logs/refs/stash >actual &&
> +	cat >expect <<-EOF &&
> +	$(test_oid zero) $oid
> +	EOF
> +	test_cmp expect actual
> +'

Then:

>  test_expect_success 'stash pop' '
>  	git reset --hard &&
>  	git stash pop &&
> -	test 3 = $(cat file) &&
> +	test 9 = $(cat file) &&
>  	test 1 = $(git show :file) &&
>  	test 1 = $(git show HEAD:file) &&
>  	test 0 = $(git stash list | wc -l)

This test was already a bit broken in needing the preceding tests, but
it will break now if REFFILES isn't true, which you can reproduce
e.g. with:

    ./t3903-stash.sh --run=1-16,18-50 -vixd

Perhaps the least sucky solution to that is:

diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
index ec9cc5646d6..1d11c9bda20 100755
--- a/t/t3903-stash.sh
+++ b/t/t3903-stash.sh
@@ -205,13 +205,19 @@ test_expect_success 'drop stash reflog updates refs/stash with rewrite' '
 	cat >expect <<-EOF &&
 	$(test_oid zero) $oid
 	EOF
-	test_cmp expect actual
+	test_cmp expect actual &&
+	>dropped-stash
 '
 
 test_expect_success 'stash pop' '
 	git reset --hard &&
 	git stash pop &&
-	test 9 = $(cat file) &&
+	if test -e dropped-stash
+	then
+		test 9 = $(cat file)
+	else
+		test 3 = $(cat file)
+	fi &&
 	test 1 = $(git show :file) &&
 	test 1 = $(git show HEAD:file) &&
 	test 0 = $(git stash list | wc -l)
Junio C Hamano Feb. 23, 2022, 9:27 p.m. UTC | #2
Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:

> This test was already a bit broken in needing the preceding tests, but
> it will break now if REFFILES isn't true, which you can reproduce
> e.g. with:
>
>     ./t3903-stash.sh --run=1-16,18-50 -vixd
>
> Perhaps the least sucky solution to that is:
>
> diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
> index ec9cc5646d6..1d11c9bda20 100755
> --- a/t/t3903-stash.sh
> +++ b/t/t3903-stash.sh
> @@ -205,13 +205,19 @@ test_expect_success 'drop stash reflog updates refs/stash with rewrite' '
>  	cat >expect <<-EOF &&
>  	$(test_oid zero) $oid
>  	EOF
> -	test_cmp expect actual
> +	test_cmp expect actual &&
> +	>dropped-stash
>  '

If "git stash drop", invoked in earlier part of this test before the
precontext, fails, then test_cmp would fail and we leave
dropped-stash untouched, even though we did run "git stash drop"
already.

Why does the next test need to depend on what has happened earlier?

>  test_expect_success 'stash pop' '
>  	git reset --hard &&
>  	git stash pop &&
> -	test 9 = $(cat file) &&
> +	if test -e dropped-stash
> +	then
> +		test 9 = $(cat file)
> +	else
> +		test 3 = $(cat file)
> +	fi &&
>  	test 1 = $(git show :file) &&
>  	test 1 = $(git show HEAD:file) &&
>  	test 0 = $(git stash list | wc -l)
John Cai Feb. 23, 2022, 9:50 p.m. UTC | #3
Hi Junio,

On 23 Feb 2022, at 16:27, Junio C Hamano wrote:

> Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:
>
>> This test was already a bit broken in needing the preceding tests, but
>> it will break now if REFFILES isn't true, which you can reproduce
>> e.g. with:
>>
>>     ./t3903-stash.sh --run=1-16,18-50 -vixd
>>
>> Perhaps the least sucky solution to that is:
>>
>> diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
>> index ec9cc5646d6..1d11c9bda20 100755
>> --- a/t/t3903-stash.sh
>> +++ b/t/t3903-stash.sh
>> @@ -205,13 +205,19 @@ test_expect_success 'drop stash reflog updates refs/stash with rewrite' '
>>  	cat >expect <<-EOF &&
>>  	$(test_oid zero) $oid
>>  	EOF
>> -	test_cmp expect actual
>> +	test_cmp expect actual &&
>> +	>dropped-stash
>>  '
>
> If "git stash drop", invoked in earlier part of this test before the
> precontext, fails, then test_cmp would fail and we leave
> dropped-stash untouched, even though we did run "git stash drop"
> already.
>
> Why does the next test need to depend on what has happened earlier?

Ideally it shouldn't, but it seems like the way these test have been written
makes subsequent tests depend on what previous tests have stashed.

I'm wondering now that while we're at it, if we should just clean up these tests
so there are no dependencies between the tests. Otherwise it's quite painful the
next time someone needs to add a test here.

We could follow the pattern in 5ac15ad2509 (reflog tests: add --updateref tests,
2021-10-16), where Ævar set up the tests and copied over the repo so each test
is isolated from each other.

>
>>  test_expect_success 'stash pop' '
>>  	git reset --hard &&
>>  	git stash pop &&
>> -	test 9 = $(cat file) &&
>> +	if test -e dropped-stash
>> +	then
>> +		test 9 = $(cat file)
>> +	else
>> +		test 3 = $(cat file)
>> +	fi &&
>>  	test 1 = $(git show :file) &&
>>  	test 1 = $(git show HEAD:file) &&
>>  	test 0 = $(git stash list | wc -l)
Ævar Arnfjörð Bjarmason Feb. 23, 2022, 9:50 p.m. UTC | #4
On Wed, Feb 23 2022, Junio C Hamano wrote:

> Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:
>
>> This test was already a bit broken in needing the preceding tests, but
>> it will break now if REFFILES isn't true, which you can reproduce
>> e.g. with:
>>
>>     ./t3903-stash.sh --run=1-16,18-50 -vixd
>>
>> Perhaps the least sucky solution to that is:
>>
>> diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
>> index ec9cc5646d6..1d11c9bda20 100755
>> --- a/t/t3903-stash.sh
>> +++ b/t/t3903-stash.sh
>> @@ -205,13 +205,19 @@ test_expect_success 'drop stash reflog updates refs/stash with rewrite' '
>>  	cat >expect <<-EOF &&
>>  	$(test_oid zero) $oid
>>  	EOF
>> -	test_cmp expect actual
>> +	test_cmp expect actual &&
>> +	>dropped-stash
>>  '
>
> If "git stash drop", invoked in earlier part of this test before the
> precontext, fails, then test_cmp would fail and we leave
> dropped-stash untouched, even though we did run "git stash drop"
> already.

Yes, that's an edge case that's exposed here, but which I thought wasn't
worth bothering with. I.e. if you get such a failure on test N getting
N+1 failing as well isn't that big of a deal.

The big deal is rather that we know we're adding a REFFILES dependency
to this, which won't run this at all, which will make the "stash pop"
below fail.

> Why does the next test need to depend on what has happened earlier?

They don't need to, and ideally wouldn't, but most of our test suite has
this issue already. Try e.g. running it with:

    prove t[0-9]*.sh :: --run=10-20 --immediate

And for this particular file running e.g. this on master:

    ./t3903-stash.sh --run=1-10,30-40

Will fail 7 tests in the 30-40 range.

So while it's ideal that we can combine tests with arbitrary --run
parameters, i.e. all tests would tear down fully, not depend on earlier
tests etc. we're really far from that being the case in practice.

So insisting on some general refactoring of this file as part of this
series seems a bit overzelous, which is why I'm suggesting the bare
minimum to expect and work around the inevitable REFFILES failure, as
Han-Wen is actively working in that area.

>>  test_expect_success 'stash pop' '
>>  	git reset --hard &&
>>  	git stash pop &&
>> -	test 9 = $(cat file) &&
>> +	if test -e dropped-stash
>> +	then
>> +		test 9 = $(cat file)
>> +	else
>> +		test 3 = $(cat file)
>> +	fi &&
>>  	test 1 = $(git show :file) &&
>>  	test 1 = $(git show HEAD:file) &&
>>  	test 0 = $(git stash list | wc -l)
Junio C Hamano Feb. 23, 2022, 10:51 p.m. UTC | #5
Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:

>> +test_expect_success 'drop stash reflog updates refs/stash' '
>> +	git reset --hard &&
>> +	git rev-parse refs/stash >expect &&
>> +	echo 9 >file &&
>> +	git stash &&
>> +	git stash drop stash@{0} &&
>> +	git rev-parse refs/stash >actual &&
>> +	test_cmp expect actual
>> +'
>
> This one will be portable to the reftable backend.
>
>> +test_expect_success 'drop stash reflog updates refs/stash with rewrite' '
>
> But as I noted in <220222.86fsob88h7.gmgdl@evledraar.gmail.com> (but it
> was easy to miss) this test will need to depend on REFFILES. So just
> changing this line to:
>
>     test_expect_success REFFILES 'drop stash[...]'
>
>> +	git reset --hard &&
>> +	echo 9 >file &&
>> +	git stash &&
>> +	oid="$(git rev-parse stash@{0})" &&
>> +	git stash drop stash@{1} &&
>> +	cut -d" " -f1-2 .git/logs/refs/stash >actual &&
>> +	cat >expect <<-EOF &&
>> +	$(test_oid zero) $oid
>> +	EOF
>> +	test_cmp expect actual
>> +'

Why should this be tested with "cut" in the first place, though?

If we start from

    stash@{0} = A
    stash@{1} = B
    stash@{2} = C

and after saying "drop stash@{1}", what we need to check is that

    stash@{0} = A
    stash@{1} = C

now holds, which can be done with "git rev-parse", and the fact that
the ref-files backend happens to record both before-and-after object
IDs is an irrelevant implementation detail, no?

I am still puzzled.
John Cai Feb. 23, 2022, 11:12 p.m. UTC | #6
Hi Junio,

On 23 Feb 2022, at 17:51, Junio C Hamano wrote:

> Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:
>
>>> +test_expect_success 'drop stash reflog updates refs/stash' '
>>> +	git reset --hard &&
>>> +	git rev-parse refs/stash >expect &&
>>> +	echo 9 >file &&
>>> +	git stash &&
>>> +	git stash drop stash@{0} &&
>>> +	git rev-parse refs/stash >actual &&
>>> +	test_cmp expect actual
>>> +'
>>
>> This one will be portable to the reftable backend.
>>
>>> +test_expect_success 'drop stash reflog updates refs/stash with rewrite' '
>>
>> But as I noted in <220222.86fsob88h7.gmgdl@evledraar.gmail.com> (but it
>> was easy to miss) this test will need to depend on REFFILES. So just
>> changing this line to:
>>
>>     test_expect_success REFFILES 'drop stash[...]'
>>
>>> +	git reset --hard &&
>>> +	echo 9 >file &&
>>> +	git stash &&
>>> +	oid="$(git rev-parse stash@{0})" &&
>>> +	git stash drop stash@{1} &&
>>> +	cut -d" " -f1-2 .git/logs/refs/stash >actual &&
>>> +	cat >expect <<-EOF &&
>>> +	$(test_oid zero) $oid
>>> +	EOF
>>> +	test_cmp expect actual
>>> +'
>
> Why should this be tested with "cut" in the first place, though?
>
> If we start from
>
>     stash@{0} = A
>     stash@{1} = B
>     stash@{2} = C
>
> and after saying "drop stash@{1}", what we need to check is that
>
>     stash@{0} = A
>     stash@{1} = C

Yes, this is true but that doesn't seem to test the --rewrite functionality.
I could be missing something, but it seems that the reflog --rewrite option
will write the LHS old oid value in the .git/logs/refs/stash file. When
--rewrite isn't used, the reflog delete still does the right thing to the
RHS entry.

I couldn't find any way to check this LFS value other than reaching into the
actual file. If there is a way that would be preferable.
>
> now holds, which can be done with "git rev-parse", and the fact that
> the ref-files backend happens to record both before-and-after object
> IDs is an irrelevant implementation detail, no?
>
> I am still puzzled.
Ævar Arnfjörð Bjarmason Feb. 23, 2022, 11:27 p.m. UTC | #7
On Wed, Feb 23 2022, John Cai wrote:

> Hi Junio,
>
> On 23 Feb 2022, at 17:51, Junio C Hamano wrote:
>
>> Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:
>>
>>>> +test_expect_success 'drop stash reflog updates refs/stash' '
>>>> +	git reset --hard &&
>>>> +	git rev-parse refs/stash >expect &&
>>>> +	echo 9 >file &&
>>>> +	git stash &&
>>>> +	git stash drop stash@{0} &&
>>>> +	git rev-parse refs/stash >actual &&
>>>> +	test_cmp expect actual
>>>> +'
>>>
>>> This one will be portable to the reftable backend.
>>>
>>>> +test_expect_success 'drop stash reflog updates refs/stash with rewrite' '
>>>
>>> But as I noted in <220222.86fsob88h7.gmgdl@evledraar.gmail.com> (but it
>>> was easy to miss) this test will need to depend on REFFILES. So just
>>> changing this line to:
>>>
>>>     test_expect_success REFFILES 'drop stash[...]'
>>>
>>>> +	git reset --hard &&
>>>> +	echo 9 >file &&
>>>> +	git stash &&
>>>> +	oid="$(git rev-parse stash@{0})" &&
>>>> +	git stash drop stash@{1} &&
>>>> +	cut -d" " -f1-2 .git/logs/refs/stash >actual &&
>>>> +	cat >expect <<-EOF &&
>>>> +	$(test_oid zero) $oid
>>>> +	EOF
>>>> +	test_cmp expect actual
>>>> +'
>>
>> Why should this be tested with "cut" in the first place, though?
>>
>> If we start from
>>
>>     stash@{0} = A
>>     stash@{1} = B
>>     stash@{2} = C
>>
>> and after saying "drop stash@{1}", what we need to check is that
>>
>>     stash@{0} = A
>>     stash@{1} = C
>
> Yes, this is true but that doesn't seem to test the --rewrite functionality.
> I could be missing something, but it seems that the reflog --rewrite option
> will write the LHS old oid value in the .git/logs/refs/stash file. When
> --rewrite isn't used, the reflog delete still does the right thing to the
> RHS entry.
>
> I couldn't find any way to check this LFS value other than reaching into the
> actual file. If there is a way that would be preferable.

Thanks for that summary that's accurate as far as I know. I think that's
how this all works, and I don't know of another way to extract this
information than this reaching behind the curtain.

Which, I think is a lot clearer if we amend the test like this. Note
that this doesn't really add anything for catching a regression goes,
but I think helps guide the human reader through this step-by-step. So
perhaps it would be good to fix the test up to have it (or maybe not):

diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
index ec9cc5646d6..bc58e99e3e6 100755
--- a/t/t3903-stash.sh
+++ b/t/t3903-stash.sh
@@ -198,12 +198,25 @@ test_expect_success 'drop stash reflog updates refs/stash' '
 test_expect_success 'drop stash reflog updates refs/stash with rewrite' '
 	git reset --hard &&
 	echo 9 >file &&
+
+	# Our two stashes
+	old_oid="$(git rev-parse stash@{0})" &&
 	git stash &&
-	oid="$(git rev-parse stash@{0})" &&
+	new_oid="$(git rev-parse stash@{0})" &&
+
+	# Our stash <old oid>/<new oid> before "drop"
+	cat >expect <<-EOF &&
+	$(test_oid zero) $old_oid
+	$old_oid $new_oid
+	EOF
+	cut -d" " -f1-2 .git/logs/refs/stash >actual &&
+	test_cmp expect actual &&
+
+	# Our stash <old oid>/<new oid> after "drop"
 	git stash drop stash@{1} &&
 	cut -d" " -f1-2 .git/logs/refs/stash >actual &&
 	cat >expect <<-EOF &&
-	$(test_oid zero) $oid
+	$(test_oid zero) $new_oid
 	EOF
 	test_cmp expect actual
 '

If this series is amended to drop the "EXPIRE_REFLOGS_REWRITE" flag then
this will fail on that last test_cmp like:
    
    + diff -u expect actual
    --- expect      2022-02-23 23:37:40.438221222 +0000
    +++ actual      2022-02-23 23:37:40.434221258 +0000
    @@ -1 +1 @@
    -0000000000000000000000000000000000000000 236c59f58e239e74e90b6832a98fa4b7f4b33647
    +5c6ad4ca28e71ae3a007e6c77043d04bc42fa9ee 236c59f58e239e74e90b6832a98fa4b7f4b33647

I.e. our <old oid> is now referring to the now-deleted stash entry we
just deleted, since we didn't rewrite it.

And as we can see with some manual inspection the state before we
dropped stash@{1} was:

    0000000000000000000000000000000000000000 5c6ad4ca28e71ae3a007e6c77043d04bc42fa9ee
    5c6ad4ca28e71ae3a007e6c77043d04bc42fa9ee 236c59f58e239e74e90b6832a98fa4b7f4b33647

My usual method of checking my assumption about this not being otherwise
inspectable would be something like:
        
    diff --git a/refs/files-backend.c b/refs/files-backend.c
    index f59589d6cce..590c13e7a2b 100644
    --- a/refs/files-backend.c
    +++ b/refs/files-backend.c
    @@ -3133,7 +3133,7 @@ static int files_reflog_expire(struct ref_store *ref_store,
            const struct object_id *oid;
     
            memset(&cb, 0, sizeof(cb));
    -       cb.rewrite = !!(expire_flags & EXPIRE_REFLOGS_REWRITE);
    +       cb.rewrite = 0;
            cb.dry_run = !!(expire_flags & EXPIRE_REFLOGS_DRY_RUN);
            cb.policy_cb = policy_cb_data;
            cb.should_prune_fn = should_prune_fn;

I.e. let's intentionally break the flag, and see what else fails (it's
set in a few places, but this is the only place where it's checked).

That should normally find the other things that are testing this, maybe
there's a better way.

But, no such luck :) The only thing that'll fail is this new test being
added here.

So just like my 5ac15ad2509 (reflog tests: add --updateref tests,
2021-10-16) this is covering a true blindspot in the "git reflog"
functionality.

The only tests that used --rewrite were a test added in c41a87dd80c
(refs: make rev-parse --quiet actually quiet, 2014-09-18), which will
pass if --rewrite is omitted.

And the ones I added in 5ac15ad2509, which I added not to test --rewrite
per-se, but to test that the --updateref part of it behaved as expected
in combination with whatever effect it was having.
Junio C Hamano Feb. 23, 2022, 11:50 p.m. UTC | #8
John Cai <johncai86@gmail.com> writes:

> Yes, this is true but that doesn't seem to test the --rewrite functionality.
> I could be missing something, but it seems that the reflog --rewrite option
> will write the LHS old oid value in the .git/logs/refs/stash file. When
> --rewrite isn't used, the reflog delete still does the right thing to the
> RHS entry.
>
> I couldn't find any way to check this LFS value other than reaching into the
> actual file. If there is a way that would be preferable.

Ah, that one.

As 2b81fab2 (git-reflog: add option --rewrite to update reflog
entries while expiring, 2008-02-22) says, the redundant half of the
reflog entry only matters to "certain sanity checks" and would not
be even visible to normal ref API users.  I wonder why we need to
even say "--rewrite" in the first place.  Perhaps we should
implicitly set it always and eventually deprecate that option.
John Cai Feb. 24, 2022, 2:53 p.m. UTC | #9
Hi Junio,

On 23 Feb 2022, at 18:50, Junio C Hamano wrote:

> John Cai <johncai86@gmail.com> writes:
>
>> Yes, this is true but that doesn't seem to test the --rewrite functionality.
>> I could be missing something, but it seems that the reflog --rewrite option
>> will write the LHS old oid value in the .git/logs/refs/stash file. When
>> --rewrite isn't used, the reflog delete still does the right thing to the
>> RHS entry.
>>
>> I couldn't find any way to check this LFS value other than reaching into the
>> actual file. If there is a way that would be preferable.
>
> Ah, that one.
>
> As 2b81fab2 (git-reflog: add option --rewrite to update reflog
> entries while expiring, 2008-02-22) says, the redundant half of the
> reflog entry only matters to "certain sanity checks" and would not
> be even visible to normal ref API users.  I wonder why we need to
> even say "--rewrite" in the first place.  Perhaps we should
> implicitly set it always and eventually deprecate that option.

Yeah, that makes sense. I had this thought as I was figuring out
how to test this. I can take care of this in a separate patch series
John Cai Feb. 24, 2022, 6:21 p.m. UTC | #10
Hi Ævar,

On 23 Feb 2022, at 16:50, Ævar Arnfjörð Bjarmason wrote:

> On Wed, Feb 23 2022, Junio C Hamano wrote:
>
>> Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:
>>
>>> This test was already a bit broken in needing the preceding tests, but
>>> it will break now if REFFILES isn't true, which you can reproduce
>>> e.g. with:
>>>
>>>     ./t3903-stash.sh --run=1-16,18-50 -vixd
>>>
>>> Perhaps the least sucky solution to that is:
>>>
>>> diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
>>> index ec9cc5646d6..1d11c9bda20 100755
>>> --- a/t/t3903-stash.sh
>>> +++ b/t/t3903-stash.sh
>>> @@ -205,13 +205,19 @@ test_expect_success 'drop stash reflog updates refs/stash with rewrite' '
>>>  	cat >expect <<-EOF &&
>>>  	$(test_oid zero) $oid
>>>  	EOF
>>> -	test_cmp expect actual
>>> +	test_cmp expect actual &&
>>> +	>dropped-stash
>>>  '
>>
>> If "git stash drop", invoked in earlier part of this test before the
>> precontext, fails, then test_cmp would fail and we leave
>> dropped-stash untouched, even though we did run "git stash drop"
>> already.
>
> Yes, that's an edge case that's exposed here, but which I thought wasn't
> worth bothering with. I.e. if you get such a failure on test N getting
> N+1 failing as well isn't that big of a deal.
>
> The big deal is rather that we know we're adding a REFFILES dependency
> to this, which won't run this at all, which will make the "stash pop"
> below fail.
>
>> Why does the next test need to depend on what has happened earlier?
>
> They don't need to, and ideally wouldn't, but most of our test suite has
> this issue already. Try e.g. running it with:
>
>     prove t[0-9]*.sh :: --run=10-20 --immediate
>
> And for this particular file running e.g. this on master:
>
>     ./t3903-stash.sh --run=1-10,30-40
>
> Will fail 7 tests in the 30-40 range.
>
> So while it's ideal that we can combine tests with arbitrary --run
> parameters, i.e. all tests would tear down fully, not depend on earlier
> tests etc. we're really far from that being the case in practice.
>
> So insisting on some general refactoring of this file as part of this
> series seems a bit overzelous, which is why I'm suggesting the bare
> minimum to expect and work around the inevitable REFFILES failure, as
> Han-Wen is actively working in that area.

Curious what your thoughts are on an effort to isolate these tests from each other.
I like your approach in t/t1417 in creating a test repo and copying it over each time.
Something like this?

diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
index ac345eced8cb..40254f8dc99c 100755
--- a/t/t3903-stash.sh
+++ b/t/t3903-stash.sh
@@ -41,7 +41,9 @@ diff_cmp () {
        rm -f "$1.compare" "$2.compare"
 }

-test_expect_success 'stash some dirty working directory' '
+test_expect_success 'setup' '
+       git init repo &&
+       cd repo &&
        echo 1 >file &&
        git add file &&
        echo unrelated >other-file &&
@@ -54,48 +56,54 @@ test_expect_success 'stash some dirty working directory' '
        test_tick &&
        git stash &&
        git diff-files --quiet &&
-       git diff-index --cached --quiet HEAD
+       git diff-index --cached --quiet HEAD &&
+       cat >expect <<-EOF &&
+       diff --git a/file b/file
+       index 0cfbf08..00750ed 100644
+       --- a/file
+       +++ b/file
+       @@ -1 +1 @@
+       -2
+       +3
+       EOF
+       cd ../
 '

-cat >expect <<EOF
-diff --git a/file b/file
-index 0cfbf08..00750ed 100644
---- a/file
-+++ b/file
-@@ -1 +1 @@
--2
-+3
-EOF
+test_stash () {
+       cp -R repo copy &&
+       cd copy &&
+       test_expect_success "$@" &&
+       cd ../ &&
+       rm -rf copy
+}

-test_expect_success 'parents of stash' '
+test_stash 'parents of stash' '
        test $(git rev-parse stash^) = $(git rev-parse HEAD) &&
        git diff stash^2..stash >output &&
        diff_cmp expect output
 '

Not sure if it's worth it though?


>
>>>  test_expect_success 'stash pop' '
>>>  	git reset --hard &&
>>>  	git stash pop &&
>>> -	test 9 = $(cat file) &&
>>> +	if test -e dropped-stash
>>> +	then
>>> +		test 9 = $(cat file)
>>> +	else
>>> +		test 3 = $(cat file)
>>> +	fi &&
>>>  	test 1 = $(git show :file) &&
>>>  	test 1 = $(git show HEAD:file) &&
>>>  	test 0 = $(git stash list | wc -l)
Ævar Arnfjörð Bjarmason Feb. 25, 2022, 11:45 a.m. UTC | #11
On Thu, Feb 24 2022, John Cai wrote:

> Hi Ævar,
>
> On 23 Feb 2022, at 16:50, Ævar Arnfjörð Bjarmason wrote:
>
>> On Wed, Feb 23 2022, Junio C Hamano wrote:
>>
>>> Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:
>>>
>>>> This test was already a bit broken in needing the preceding tests, but
>>>> it will break now if REFFILES isn't true, which you can reproduce
>>>> e.g. with:
>>>>
>>>>     ./t3903-stash.sh --run=1-16,18-50 -vixd
>>>>
>>>> Perhaps the least sucky solution to that is:
>>>>
>>>> diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
>>>> index ec9cc5646d6..1d11c9bda20 100755
>>>> --- a/t/t3903-stash.sh
>>>> +++ b/t/t3903-stash.sh
>>>> @@ -205,13 +205,19 @@ test_expect_success 'drop stash reflog updates refs/stash with rewrite' '
>>>>  	cat >expect <<-EOF &&
>>>>  	$(test_oid zero) $oid
>>>>  	EOF
>>>> -	test_cmp expect actual
>>>> +	test_cmp expect actual &&
>>>> +	>dropped-stash
>>>>  '
>>>
>>> If "git stash drop", invoked in earlier part of this test before the
>>> precontext, fails, then test_cmp would fail and we leave
>>> dropped-stash untouched, even though we did run "git stash drop"
>>> already.
>>
>> Yes, that's an edge case that's exposed here, but which I thought wasn't
>> worth bothering with. I.e. if you get such a failure on test N getting
>> N+1 failing as well isn't that big of a deal.
>>
>> The big deal is rather that we know we're adding a REFFILES dependency
>> to this, which won't run this at all, which will make the "stash pop"
>> below fail.
>>
>>> Why does the next test need to depend on what has happened earlier?
>>
>> They don't need to, and ideally wouldn't, but most of our test suite has
>> this issue already. Try e.g. running it with:
>>
>>     prove t[0-9]*.sh :: --run=10-20 --immediate
>>
>> And for this particular file running e.g. this on master:
>>
>>     ./t3903-stash.sh --run=1-10,30-40
>>
>> Will fail 7 tests in the 30-40 range.
>>
>> So while it's ideal that we can combine tests with arbitrary --run
>> parameters, i.e. all tests would tear down fully, not depend on earlier
>> tests etc. we're really far from that being the case in practice.
>>
>> So insisting on some general refactoring of this file as part of this
>> series seems a bit overzelous, which is why I'm suggesting the bare
>> minimum to expect and work around the inevitable REFFILES failure, as
>> Han-Wen is actively working in that area.
>
> Curious what your thoughts are on an effort to isolate these tests from each other.
> I like your approach in t/t1417 in creating a test repo and copying it over each time.
> Something like this?

That looks good to me if you're willing to do that legwork, probably
better in a preceding cleanup commit.

> diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
> index ac345eced8cb..40254f8dc99c 100755
> --- a/t/t3903-stash.sh
> +++ b/t/t3903-stash.sh
> @@ -41,7 +41,9 @@ diff_cmp () {
>         rm -f "$1.compare" "$2.compare"
>  }
>
> -test_expect_success 'stash some dirty working directory' '
> +test_expect_success 'setup' '
> +       git init repo &&
> +       cd repo &&
>         echo 1 >file &&
>         git add file &&
>         echo unrelated >other-file &&
> @@ -54,48 +56,54 @@ test_expect_success 'stash some dirty working directory' '
>         test_tick &&
>         git stash &&
>         git diff-files --quiet &&
> -       git diff-index --cached --quiet HEAD
> +       git diff-index --cached --quiet HEAD &&
> +       cat >expect <<-EOF &&

nit: you can add \ to that, i.e. <<-\EOF. Helps readability, i.e.  it's
obvious right away that no variables are in play..

> +       diff --git a/file b/file
> +       index 0cfbf08..00750ed 100644
> +       --- a/file
> +       +++ b/file
> +       @@ -1 +1 @@
> +       -2
> +       +3
> +       EOF
> +       cd ../
>  '
>
> -cat >expect <<EOF
> -diff --git a/file b/file
> -index 0cfbf08..00750ed 100644
> ---- a/file
> -+++ b/file
> -@@ -1 +1 @@
> --2
> -+3
> -EOF
> +test_stash () {
> +       cp -R repo copy &&
> +       cd copy &&
> +       test_expect_success "$@" &&
> +       cd ../ &&
> +       rm -rf copy
> +}
>
>
> -test_expect_success 'parents of stash' '
> +test_stash 'parents of stash' '
>         test $(git rev-parse stash^) = $(git rev-parse HEAD) &&
>         git diff stash^2..stash >output &&
>         diff_cmp expect output
>  '

For this sort of thing I think it's usually better to override
"test_expect_success" as a last resort, i.e. to have that
"test_setup_stash_copy" just be a "setup_stash" or whatever function
called from within your test_expect_success.

And instead of the "rm -rf" later, just do:

    test_when_finished "rm -rf copy" &&
    cp -R repo copy &&
    [...]

The test still needs to deal with the sub-repo, but it could cd or use
"-C".

It's bad to add "cd .." in a &&-chain, because if earlier steps fail
we're in the wrong directory for the next test, so either -C or a
sub-shell...

> Not sure if it's worth it though?

Maybe not, which is why I suggested upthread to maybe go for some
smallest possible change here and focus on the lib-ificitaion :)

>
>
>>
>>>>  test_expect_success 'stash pop' '
>>>>  	git reset --hard &&
>>>>  	git stash pop &&
>>>> -	test 9 = $(cat file) &&
>>>> +	if test -e dropped-stash
>>>> +	then
>>>> +		test 9 = $(cat file)
>>>> +	else
>>>> +		test 3 = $(cat file)
>>>> +	fi &&
>>>>  	test 1 = $(git show :file) &&
>>>>  	test 1 = $(git show HEAD:file) &&
>>>>  	test 0 = $(git stash list | wc -l)
Junio C Hamano Feb. 25, 2022, 5:23 p.m. UTC | #12
Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:

>> Curious what your thoughts are on an effort to isolate these tests from each other.
>> I like your approach in t/t1417 in creating a test repo and copying it over each time.
>> Something like this?
>
> That looks good to me if you're willing to do that legwork, probably
> better in a preceding cleanup commit.

Yup.  Thanks for helping other contributors.  I agree with many
things you said in your review.

>> diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
>> index ac345eced8cb..40254f8dc99c 100755
>> --- a/t/t3903-stash.sh
>> +++ b/t/t3903-stash.sh
>> @@ -41,7 +41,9 @@ diff_cmp () {
>>         rm -f "$1.compare" "$2.compare"
>>  }
>>
>> -test_expect_success 'stash some dirty working directory' '
>> +test_expect_success 'setup' '
>> +       git init repo &&
>> +       cd repo &&

We do not want to "chdir" around without isolating it in a
subprocess.  If this test fails after it goes to "repo" but before it
does "cd ..", the next test begins in the "repo" directory, but it
is most likely not expecting that.

>> -cat >expect <<EOF
>> -diff --git a/file b/file
>> -index 0cfbf08..00750ed 100644
>> ---- a/file
>> -+++ b/file
>> -@@ -1 +1 @@
>> --2
>> -+3
>> -EOF
>> +test_stash () {
>> +       cp -R repo copy &&
>> +       cd copy &&
>> +       test_expect_success "$@" &&
>> +       cd ../ &&
>> +       rm -rf copy
>> +}

This will create an anti-pattern, because you would want to have the
part between "cd copy" and "cd .." in a subshell, but you do not
want to do test_expect_success inside a subshell.  Hence, this is a
bad helper that does not help and should not be used, I would think.

>> -test_expect_success 'parents of stash' '
>> +test_stash 'parents of stash' '
>>         test $(git rev-parse stash^) = $(git rev-parse HEAD) &&
>>         git diff stash^2..stash >output &&
>>         diff_cmp expect output
>>  '
>
> For this sort of thing I think it's usually better to override
> "test_expect_success" as a last resort, i.e. to have that
> "test_setup_stash_copy" just be a "setup_stash" or whatever function
> called from within your test_expect_success.
>
> And instead of the "rm -rf" later, just do:
>
>     test_when_finished "rm -rf copy" &&
>     cp -R repo copy &&
>     [...]

Yup.  I think this is how we would write it:

	test_expect_success 'parents of stash' '
		test_when_finished "rm -fr copy" &&
		cp -R repo copy &&
		(
			cd copy &&
			... the real body of the test here, like ...
			test $(git rev-parse stash^) = $(git rev-parse HEAD) &&
		)
	'

> The test still needs to deal with the sub-repo, but it could cd or use
> "-C".

I am not sure about this.  test_expect_success does not take "-C".
diff mbox series

Patch

diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
index b149e2af441..ec9cc5646d6 100755
--- a/t/t3903-stash.sh
+++ b/t/t3903-stash.sh
@@ -185,10 +185,33 @@  test_expect_success 'drop middle stash by index' '
 	test 1 = $(git show HEAD:file)
 '
 
+test_expect_success 'drop stash reflog updates refs/stash' '
+	git reset --hard &&
+	git rev-parse refs/stash >expect &&
+	echo 9 >file &&
+	git stash &&
+	git stash drop stash@{0} &&
+	git rev-parse refs/stash >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'drop stash reflog updates refs/stash with rewrite' '
+	git reset --hard &&
+	echo 9 >file &&
+	git stash &&
+	oid="$(git rev-parse stash@{0})" &&
+	git stash drop stash@{1} &&
+	cut -d" " -f1-2 .git/logs/refs/stash >actual &&
+	cat >expect <<-EOF &&
+	$(test_oid zero) $oid
+	EOF
+	test_cmp expect actual
+'
+
 test_expect_success 'stash pop' '
 	git reset --hard &&
 	git stash pop &&
-	test 3 = $(cat file) &&
+	test 9 = $(cat file) &&
 	test 1 = $(git show :file) &&
 	test 1 = $(git show HEAD:file) &&
 	test 0 = $(git stash list | wc -l)