git/list[1] front-page[2] threads[3] people[4] search[5] about
 

Re: test &&-chain lint

From
Jeff King <peff@peff.net>
Date
Mar 20, 2015, 17:24 UTC
Message-ID
<20150320172406.GA15172@peff.net>
In-Reply-To
<xmqq384zha6s.fsf@gitster.dls.corp.google.com>
On Fri, Mar 20, 2015 at 10:04:43AM -0700, Junio C Hamano wrote:
Show 5 quoted lines
> One case where this might misdetect a good test would be this one:
> 
>     test_expect_success 'either succeed or fail with status 1' '
> 	git subcmd || case "$?" in 1) : happy ;; *) false failure ;; esac
>     '

Yes. Any use of "||" is going to cause problems. See the conversions in the series I posted a few hours ago. The solution is to adapt the test style to encase any "||" inside a block. I don't like having to modify the tests to match our lint check, but it is a fairly rare case, and the modification is not complex. And in fact I think it often highlights to the reader that something funny is going on; a snippet like the one above is OK to run at the start of a test, but:

  foo &&
  bar ||
  case "$?"
   ...

is potentially dangerous. You do not know if you are checking the $? of "foo" or "bar" in that case statement.

Your case above is actually better spelled as test_expect_code, but there are more complex one-off cases that I solved using a {} block.

> I wonder if another valid way to make it harder for us to commit
> "broken && chain" errors in our test may be to make it not an error
> in the first place.  Do we know how buggy various implementations of
> shells are with respect to their handling of "set -e"?

Certainly that is a thought that occurred to me while writing the series. At GitHub, we have a shell-based test harness for internal projects that is somewhat like Git's t/test-lib.sh, but uses "-e" to catch errors.

It's mostly OK, but there are some surprising bits. For instance, try this sequence of snippets:

     set -e
     false
     echo should not reach
That should print nothing. So far so good. How about in a subshell:
     set -e
     (
       false
       echo 1
     )
     echo 2

That also prints nothing. Good. But "set -e" is suppressed in a conditional or in the presence of logic operators like "||". So you'd expect this:

     set -e
     (
       false
       echo 1
     ) || {
         echo outcome=$?
         false
     }
     echo 2

to break out of the subshell, print the failed outcome, and then exit. But it doesn't. It prints "1" and "2". The suppression of "set -e" continues inside the subshell, even though the inner commands are not part of their own conditionals.

OK, so when we open a subshell we have to re-iterate our desire for "set -e":

    set -e
    (
      set -e
      false
      echo 1
    ) || {
        echo outcome=$?
	false
    }
    echo 2

Nope, does nothing, at least in bash and dash. The ||-operator suppression is clearly not "turn off set -e", but "globally make -e useless inside here".

So you end up having to use manual exits to jump out of the subshell, like:

     set -e
     (
       false || exit 1
       echo 1
     ) || {
         echo outcome=$?
         false
     }
     echo 2
That snippet produces "outcome=1", which is what I expect.

Of course most tests are not so complicated to have subshells with conditional operators on the side. But I did not just type out that example right now from scratch. I cut-and-pasted it from a real-world commit message.

So I dunno. I think "set -e" is kind of a dangerous lure. It works so well _most_ of the time that you start to rely on it, but it really does have some funny corner cases (even on modern shells, and for all I know, the behavior above is mandated by POSIX).

-Peff
Previous: Junio C HamanoNext: Junio C Hamano
Message 21 of 35 in “not making corruption worse”
  1. 0/5 not making corruption worseJeff King, Mar 17, 2015
  2. 1/5 t5312: test object deletion code paths in a corrupted repositoryJeff King, Mar 17, 2015
  3. Johannes SixtMar 17, 2015
  4. Jeff KingMar 17, 2015
  5. Johannes SixtMar 18, 2015
  6. Junio C HamanoMar 19, 2015
  7. Jeff KingMar 19, 2015
  8. Junio C HamanoMar 19, 2015
  9. Jeff KingMar 19, 2015
  10. Junio C HamanoMar 19, 2015
  11. Jeff KingMar 19, 2015
  12. Eric SunshineMar 20, 2015
  13. Jeff KingMar 20, 2015
  14. Eric SunshineMar 20, 2015
  15. test &&-chain lint (was: [PATCH 1/5] t5312: test object deletion code paths in a corrupted repository)Jeff King, Mar 20, 2015
  16. Jeff KingMar 20, 2015
  17. Jeff KingMar 20, 2015
  18. Eric SunshineMar 20, 2015
  19. Junio C HamanoMar 20, 2015
  20. Junio C HamanoMar 20, 2015
  21. Jeff KingMar 20, 2015
  22. Junio C HamanoMar 20, 2015
  23. Jeff KingMar 20, 2015
  24. 2/5 refs: introduce a "ref paranoia" flagJeff King, Mar 17, 2015
  25. Junio C HamanoMar 19, 2015
  26. Jeff KingMar 19, 2015
  27. Junio C HamanoMar 19, 2015
  28. Jeff KingMar 19, 2015
  29. 3/5 prune: turn on ref_paranoia flagJeff King, Mar 17, 2015
  30. 4/5 repack: turn on "ref paranoia" when doing a destructive repackJeff King, Mar 17, 2015
  31. 5/5 refs.c: drop curate_packed_refsJeff King, Mar 17, 2015
  32. Eric SunshineMar 20, 2015
  33. Jeff KingMar 17, 2015
  34. Junio C HamanoMar 17, 2015
  35. Jeff KingMar 18, 2015

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.