Skip to content

negation in ocamltest#13315

Closed
gasche wants to merge 8 commits intoocaml:trunkfrom
gasche:ocamltest-negation
Closed

negation in ocamltest#13315
gasche wants to merge 8 commits intoocaml:trunkfrom
gasche:ocamltest-negation

Conversation

@gasche
Copy link
Copy Markdown
Member

@gasche gasche commented Jul 18, 2024

I was inspired by #13248 and a discussion with @shindere, @OlivierNicole, @dra27, @damiendoligez and @Octachron to implement the proposal of @OlivierNicole for negated tests in ocamltest script syntax.

Current state in trunk

Today the syntax of OCaml test is <statements> <blocks>, where statements look like foo; and are run in sequence, and blocks look like { ... } and are run only if the statements pass, and not if they skip or fail.

The proposal of @OlivierNicole in #13248 is to add negated sub-tests !{ ... } that run when the statements skip, instead of passing. In particular, foo; { bar } !{ baz } will run bar if foo passess, and baz if it skips. (It fails if foo fails.)

After implementing this I iterated and ended with a different design, with a new construct not <action>; and proper keywords instead of ! for readability: else { ... }, and then { ... } (where then can in fact be omitted).

New constructs in this PR

This PR introduces not <action>;, then {<block>} and else {<block>}.

Sematics of not <action>;: it runs action, and skips if the action passes and passes if the action skips.

Examples of not: not-windows; can be written as not windows;, no-tsan can be written as not tsan;.

Semantics of then, else blocks:

  • if one of the actions before the block skipped, we skip then blocks and execute else blocks (in parallel)
  • if none of the actions before the block skipped, we execute then blocks (in parallel) and skip else blocks

(In particular the semantics of then { ... } is exactly the same as { ... }.)

For example, the following test (a part of tests/backtrace/backtrace_dynlink.ml):

run;
 {
   no-flambda;
   check-program-output;
 }{
   reference = "${test_source_directory}/backtrace_dynlink.flambda.reference";
   flambda;
   check-program-output;
 }

can be rewritten as:

 run;
 flambda;
 then {
   reference = "${test_source_directory}/backtrace_dynlink.flambda.reference";
   check-program-output;
 }
 else {
   check-program-output;
 }

History of the PR

First iteration

At first I implemented negation as proposed by @OlivierNicole. And then I tried to adapt existing tests to follow this syntax, and sometimes it is nice, for example:

{ flambda; ... }
{ not-flambda; ... }

becomes:

flambda;
{ ... }
!{ ... }

which avoids a repetition.

But I also realized that this does not remove the need for not-windows as I had hoped, due to the following pattern:

not-windows;
<a lot of other statements>

which I do not want to convert into the following:

not-windows;
!{
  <a lot of other statements, indented>
}

Second iteration

The second step was to introduce an explicit negation for statements (not sub-tests). I could write !windows; for an action that passes if windows skips and skip if windows passes. But then one can write both !windows; and !{ windows; } (only in some contexts) and they have radically different meanings, which is not nice.

So in the end I propose a different syntax, inspired by a proposal by @damiendoligez during lunch: not foo; for the negation of an action, and else { ... } for a negated sub-child. I also allow then { ... } for consistency, but this is optional.

The content of this PR

In this PR:

  1. I implement the first iteration of the feature, !{ ... }
  2. I convert some tests to !{ ... } to get a sense of what they look like.
  3. I replace !{ ... } by else { ... } and add support for then { ... } and not ...;
  4. I use it to replace the not-foo and no-bar actions from the testsuite with not foo and not bar instead. (But for now they remain supported as actions.)

(There is one occurrence of not_macos_amd64_tsan which I left around, in part because there is no macos_amd64_tsan action today, and in part because replacing this properly would require not (macos && amd64 && tsan), or not { macos; arch_amd64; tsan }, and I don't care to add support for this right now.)

The PRs #13316 and #13317 were previously part of the present PR and split off for independent review. #13316 was merged, but #13317 is still waiting for a review. It's a small PR, come on!

@shindere
Copy link
Copy Markdown
Contributor

shindere commented Jul 19, 2024 via email

@gasche
Copy link
Copy Markdown
Member Author

gasche commented Sep 4, 2024

Gentle ping: I have split two smaller PRs from this PR. (The commits are included there and also here, so reviewing them first is easier.)

@gasche
Copy link
Copy Markdown
Member Author

gasche commented Sep 25, 2024

@shindere gentle ping . This PR still needs a review, and the two "smaller" PRs below it are #13316, which was reviewed by @MisterDA but needs a maintainer approval, and #13317 which needs a review.

@gasche gasche mentioned this pull request Sep 27, 2024
@gasche gasche force-pushed the ocamltest-negation branch from 54622bb to 4a396c9 Compare October 1, 2024 13:30
@gasche gasche force-pushed the ocamltest-negation branch from 4a396c9 to 78c8e86 Compare October 1, 2024 13:37
@OlivierNicole
Copy link
Copy Markdown
Contributor

Hello, this is on my review list but it’s taking me time to get there. If someone else wants to review these first, feel free, just please let me know so we don’t duplicate work.

@gasche
Copy link
Copy Markdown
Member Author

gasche commented Feb 4, 2025

I remain interested in having someone review this. @OlivierNicole, if you are not available for an implementation review (which I can very well understand), I would still be interested in your opinion on the proposed design, which I think you could form more easily. (Maybe @lukemaurer has an opinion as well as this is tangentially related to #13259.)

If there is consensus among ocamltest users that this is a reasonable feature to have, I can probably find someone else that cares less about ocamltest and somehow extract an implementation review out of them.

@gasche
Copy link
Copy Markdown
Member Author

gasche commented Feb 4, 2025

I rephrased the original message of this PR to emphasize what new constructs are proposed, with short usage examples. (The previous description, which is kept after that, is more about the history of changes that lead to this proposal, which makes sense in the context of a specific discussion about a slightly different proposal from @OlivierNicole.)

@lukemaurer
Copy link
Copy Markdown
Contributor

Hmm! Seems useful, though I can see a couple of pitfalls: You could write

windows;
then { (* win-only tests *) }
else { (* non-win tests *) }

and then someone adds a new condition somewhere higher in the file, and if I understand correctly, if that new condition fails, you're going to end up running the non-Windows tests on Windows. This suggests introducing an explicit if to the syntax to tie to then and else?

Also if someone writes

{ A }
else { B }

it will look like else is sort of like catch: do the things in A and if they fail/skip then do the things in B. Possibly then should be required in this case?

@gasche
Copy link
Copy Markdown
Member Author

gasche commented Feb 5, 2025

Thanks for the feedback.

One difficulty with introducing an if is that currently it's easy to implement conjunction by just running the two checks in sequence, but I cannot do this with if anymore -- or I have to implement boolean operators, meh. On the other hand, it's possible to scope the conditional part:

{
  windows;
  flambda;
  then { ... } /* windows and flamdba */
  else { ... } /* not windows or not flambda */
}

@lukemaurer
Copy link
Copy Markdown
Contributor

One difficulty with introducing an if is that currently it's easy to implement conjunction by just running the two checks in sequence, but I cannot do this with if anymore -- or I have to implement boolean operators, meh.

Hmm ... I had been thinking if { windows; flambda; } then { ... } else { ... } but then you have to restrict the grammar of the if part so that it doesn't have branches (or you no longer have a tree structure for your dependencies).

On the other hand, it's possible to scope the conditional part:

Yeah, that seems good. So if the surrounding block is skipped, then the whole then/else is skipped, but once we enter the block, either the then or the else must happen.

@OlivierNicole
Copy link
Copy Markdown
Contributor

OlivierNicole commented Feb 7, 2025

Sorry for the delay in checking out this design.

Semantics of then, else blocks:

  • if one of the actions before the block skipped, we skip then blocks and execute else blocks (in parallel)

  • if none of the actions before the block skipped, we execute then blocks (in parallel) and skip else blocks

I am unsure about what is done with the else blocks. My mental model of a test spec is that each several actions in sequence are like successive section titles of decreasing importance, and blocks are inside the latest section and their order doesn’t matter. With that mental model, I agree with the semantics of then { ... }, but I would have expected else { ... } to be run only if the last action skipped or failed.

Doing so would avoid the behavior that @lukemaurer finds counter-intuitive (I find it odd, too). It seems to me that it would make the semantics of action1; action2; action3; then { ... } else { ... } pretty much like a sequence of two operations that can fail, followed by an ordinary conditional. It makes things easier to read and less error-prone, at least for me.

I guess @lukemaurer’s proposal of having blocks as conditions is even better as it would allow for conjunctions.

@gasche
Copy link
Copy Markdown
Member Author

gasche commented Feb 7, 2025

I don't mind going with if { <sequence> } ((then | else) { <block> })* as suggested. (There can be several then or else branch, independent of / parallel to each other.) Maybe we should use ( <sequence> ) instead of { <sequence> } to highlight that this is less expressive than a full block -- no parallelism/independence inside.

@gasche
Copy link
Copy Markdown
Member Author

gasche commented Jan 29, 2026

I started splitting this PR to make progress again, in particular #14502 implements the easy part, which is not <action>. We'll see about conditionals as a second step.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants