Skip to content

Conversation

simonbyrne
Copy link
Member

Another attempt at #51012

@simonbyrne simonbyrne force-pushed the sb/test-scope branch 6 times, most recently from 1c67443 to b1fec32 Compare February 27, 2024 05:38
@simonbyrne
Copy link
Member Author

@vchuravy @Keno I'm getting some weird failures on this branch originating from this line:

@assert current_mapping == val

The simplest reproducer I have is:

using Test
@testset begin
    @testset for r in Int[1]
    end
end

@simonbyrne
Copy link
Member Author

Blocked by #53521

@simonbyrne simonbyrne force-pushed the sb/test-scope branch 2 times, most recently from 73b39d1 to 0cd361c Compare March 9, 2024 01:00
@nickrobinson251
Copy link
Contributor

Would love to see this merged!

@simonbyrne do you have any time to work on this? I'd be happy to rebase and help get it over the line. I think rebasing, and then deciding what to do with the commented-out scope tests might be all that's needed here?

@giordano giordano added testsystem The unit testing framework and Test stdlib stdlib Julia's standard library labels Sep 23, 2024
@giordano
Copy link
Member

I initially thought I managed to badly botch the rebase, but I believe this is now failing to build because after the rebase #55452 was merged in and that PR removed using .ScopedValues inside Base, so imports need to change a little bit. I'll leave this exercise to someone else 😁

@simonbyrne
Copy link
Member Author

Sorry, it still needs some work. The handling of errors is wasn't quite correct, and the resetting of rng state is kind of confusing (arguably should be handled differently)

@simonbyrne simonbyrne marked this pull request as ready for review October 5, 2024 03:51
@simonbyrne
Copy link
Member Author

Currently blocked by #56062

@d-netto
Copy link
Member

d-netto commented Jan 30, 2025

FYI, #56062 was recently fixed.

@simonbyrne
Copy link
Member Author

Okay, I think this is now passing. Any thoughts?

@simonbyrne
Copy link
Member Author

@nanosoldier runtests()

Copy link
Member

@giordano giordano left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even though I recently touched the Test stdlib I'm not particularly familiar with its code, I don't understand some of the design choices. That said, this looks overall good to me (as much as that mean, not knowing the code), looks like a decent simplification. I'm going to trust hope existing tests already cover corner cases of nested testsets that we should make sure keep working.

@test sprint(show, Core.current_scope()) == "nothing"
@test sprint(show, ScopedValue{Int}()) == "$ScopedValue{$Int}(undefined)"
@test sprint(show, sval) == "$ScopedValue{$Int}(1)"
# @test sprint(show, Core.current_scope()) == "nothing"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this test commented out? Can we move it at the top-level, outside of any testset, to keep the test, or that's still within an implicit scope?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Depending on how the runtests is called, it might be inside a scope. The only way to guarantee it is to run it in a separate process.

@test sprint(show, sval) == "$ScopedValue{$Int}(2)"
objid = sprint(show, Base.objectid(sval))
@test sprint(show, Core.current_scope()) == "Base.ScopedValues.Scope(Base.ScopedValues.ScopedValue{$Int}@$objid => 2)"
# @test sprint(show, Core.current_scope()) == "Base.ScopedValues.Scope(Base.ScopedValues.ScopedValue{$Int}@$objid => 2)"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also this: why was it removed?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same, I can't see a way to make this work.

Comment on lines -1952 to -2155
"""
push_testset(ts::AbstractTestSet)
Adds the test set to the `task_local_storage`.
"""
function push_testset(ts::AbstractTestSet)
testsets = get(task_local_storage(), :__BASETESTNEXT__, AbstractTestSet[])
push!(testsets, ts)
setindex!(task_local_storage(), testsets, :__BASETESTNEXT__)
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apparently some (testing) packages were using this: https://juliahub.com/ui/Search?type=symbols&q=push_testset&u=use. Presumably it's ok to ask them to adapt their use of internal functions? These aren't documented.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, unfortunately I don't see how we could keep that working

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would one update a codebase that is using push_testset? is it as simple as changing from

push_testset(ts)
try
   ...
finally
  pop_testset()
end

to

@with_testset ts begin
  ...
end

?

(p.s. Very pleased to see this improvement!)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that should work I think.

Comment on lines -1779 to +1957
get_testset_depth() > 1 ? rethrow() : failfast_print()
get_testset_depth() > 0 ? rethrow() : failfast_print()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't immediately tell the motivation for this change, could you please elaborate?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously the check used to be inside the scope, now it is outside

@nanosoldier
Copy link
Collaborator

The package evaluation job you requested has completed - possible new issues were detected.
The full report is available.

Report summary

❗ Packages that crashed

3 packages crashed only on the current version.

  • An internal error was encountered: 1 packages
  • A segmentation fault happened: 2 packages

14 packages crashed on the previous version too.

✖ Packages that failed

181 packages failed only on the current version.

  • Package fails to precompile: 19 packages
  • Package has test failures: 3 packages
  • Package tests unexpectedly errored: 133 packages
  • Test duration exceeded the time limit: 26 packages

1132 packages failed on the previous version too.

✔ Packages that passed tests

22 packages passed tests only on the current version.

  • Other: 22 packages

5158 packages passed tests on the previous version too.

~ Packages that at least loaded

18 packages successfully loaded only on the current version.

  • Other: 18 packages

2933 packages successfully loaded on the previous version too.

➖ Packages that were skipped altogether

897 packages were skipped on the previous version too.

Keno added a commit that referenced this pull request Aug 24, 2025
Test has `Test.record_passes()` which gets `OncePerProcess` initialized
to the value of `ENV["JULIA_TEST_RECORD_PASSES"]`. However, in another
palce the test suite once to override this value for its scope, and does
this by setting the environment variable, relying on OncePerProcess
picking this up. However, this is not at all semantically guaranteed
and of course fails if anybody happened to have a testset before
(which is possible, because this is used by Base.runtests). It would
be much better if we could use a ScopedValue for this like the other
`Test` setting state (after #53462). However, there currently isn't
any way to once-initialize the ScopedValue default. This PR aims to
fix that by letting the scoped value default be an evaluated callback.
The original `ScopedValue` struct is renamed `LazyScopedValue`, but
the `ScopedValue` name is retained with the same API as an alias
to a `LazyScopedValue` that just uses `Returns(default)` as its
initializer. It might be a bit weird that the default could return
a different value at every time, but I think a reasonable way
to think about this is that the default chains to some dynamic
state which is expressed in this callback.
@Keno
Copy link
Member

Keno commented Aug 24, 2025

Sigh, I don't understand the test failures here. I can't reproduce them locally and worse, #59372 which includes these commits doesn't show them either - and yet - the tests failed on all platforms on CI.

@Keno
Copy link
Member

Keno commented Aug 24, 2025

I'm gonna go with testsets are not thread safe, and before this change, they did not propagate into @threads, but now they do.

@Keno
Copy link
Member

Keno commented Aug 24, 2025

  TaskFailedException
      nested task error: ConcurrencyViolationError("Vector has invalid state. Don't modify internal fields incorrectly, or resize without correct locks")
      Stacktrace:
        [1] _growend_internal!(a::Vector{Any}, delta::Int64, len::Int64)
          @ Base ./array.jl:1141
        [2] _growend!
          @ ./array.jl:1181 [inlined]
        [3] push!
          @ ./array.jl:1320 [inlined]
        [4] record

The retry confirmed that theory

Keno added a commit that referenced this pull request Aug 24, 2025
In preparation of #53462, ensure that attempting to record test results
to a test set from multiple threads does not cause corruption. Note
that other part of `Test` remain non-threadsafe.
@Keno Keno removed the merge me PR is reviewed. Merge when all tests are passing label Aug 25, 2025
Keno added a commit that referenced this pull request Aug 25, 2025
In preparation of #53462, ensure that attempting to record test results
to a test set from multiple threads does not cause corruption. Note
that other part of `Test` remain non-threadsafe.
Keno pushed a commit that referenced this pull request Aug 25, 2025
@Keno
Copy link
Member

Keno commented Aug 25, 2025

I have rebased on top of #59374, in the hope that that will fix CI and the expectation that that'll get merged first.

Keno added a commit that referenced this pull request Aug 25, 2025
In preparation of #53462, ensure that attempting to record test results
to a test set from multiple threads does not cause corruption. Note
that other part of `Test` remain non-threadsafe.
Keno added a commit that referenced this pull request Aug 25, 2025
In preparation of #53462, ensure that attempting to record test results
to a test set from multiple threads does not cause corruption. Note
that other part of `Test` remain non-threadsafe.
Keno added a commit that referenced this pull request Aug 25, 2025
Test has `Test.record_passes()` which gets `OncePerProcess` initialized
to the value of `ENV["JULIA_TEST_RECORD_PASSES"]`. However, in another
palce the test suite once to override this value for its scope, and does
this by setting the environment variable, relying on OncePerProcess
picking this up. However, this is not at all semantically guaranteed
and of course fails if anybody happened to have a testset before
(which is possible, because this is used by Base.runtests). It would
be much better if we could use a ScopedValue for this like the other
`Test` setting state (after #53462). However, there currently isn't
any way to once-initialize the ScopedValue default. This PR aims to
fix that by letting the scoped value default be an evaluated callback.
@Keno Keno added merge me PR is reviewed. Merge when all tests are passing and removed merge me PR is reviewed. Merge when all tests are passing labels Aug 25, 2025
Keno added a commit that referenced this pull request Aug 25, 2025
In preparation of #53462, ensure that attempting to record test results
to a test set from multiple threads does not cause corruption. Note that
other part of `Test` remain non-threadsafe.
@Keno Keno merged commit bb36851 into master Aug 26, 2025
7 checks passed
@Keno Keno deleted the sb/test-scope branch August 26, 2025 15:06
@simonbyrne
Copy link
Member Author

Thanks Keno for seeing this through!

@NHDaly
Copy link
Member

NHDaly commented Aug 28, 2025

Amazing, I'm glad to see this! 💪 This is a really nice cleanup, and a perfect showcase for ScopedValues. 👏

Keno added a commit that referenced this pull request Aug 30, 2025
Test has `Test.record_passes()` which gets `OncePerProcess` initialized
to the value of `ENV["JULIA_TEST_RECORD_PASSES"]`. However, in another
palce the test suite once to override this value for its scope, and does
this by setting the environment variable, relying on OncePerProcess
picking this up. However, this is not at all semantically guaranteed
and of course fails if anybody happened to have a testset before
(which is possible, because this is used by Base.runtests). It would
be much better if we could use a ScopedValue for this like the other
`Test` setting state (after #53462). However, there currently isn't
any way to once-initialize the ScopedValue default. This PR aims to
fix that by letting the scoped value default be an evaluated callback.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
stdlib Julia's standard library testsystem The unit testing framework and Test stdlib
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants