Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

limit the scope of srand in tests #16940

Merged
merged 1 commit into from
Oct 8, 2017
Merged

limit the scope of srand in tests #16940

merged 1 commit into from
Oct 8, 2017

Conversation

rfourquet
Copy link
Member

(Cf. #8339 for context).
The purpose of this change is to solve the following problem: someone adds a test containg a call to srand(123) (in order to test a particular configuration). Then every new test added below that one will be "infected" by this srand call: the tester will use rand() with the intention to test any random value, but didn't notice that rand() had become deterministic at that point; only one unique value will ever be tested. This limits dramatically the coverage of the tests.

An objection against changing this behavior was that with ever-changing random values, a failed test cannot be reproduced: this is addressed by #16924.

I'm not quite sure of what the API should be. Here is the current implemented solution:

guardsrand() do
    srand(123)
    x = ...
    @test rand() == x
end
# here the global RNG is restored to its state before the call to guardsrand

A shortcut is

srand(123) do
    x =...
    @test rand() == x
end

I thought srandguard is necessary over simply this overload of srand, as I think we don't want the following to be valid

srand() do
    # in this scope, srand() has been called
   x = ...
   @test rand() == x # here rand() is unpredictable and not reproducible
end

So an explicit call to srand() within guardsrand would be required.

I updated some test files when it's obvious what to do, but almost didn't touch the test files in linalg (not always clear to me what is the intention of srand). I guess some instances could simply be removed if #16924 gets merged.

An alternative simpler implementation could be to have a function reseed() = srand(GLOBAL_RNG.seed), to close the scope of a previous call to srand (but I find it less clean).

Note: branched off unmerged #16919, only one new commit here.

@tkelman tkelman added the randomness Random number generation and the Random stdlib label Jun 15, 2016
@tkelman
Copy link
Contributor

tkelman commented Jun 15, 2016

This should probably restore the RNG state even on an exception, to be consistent with the other do-block forms.

@simonbyrne
Copy link
Contributor

Seems useful, but I don't really understand the problem with overloading srand here.

@rfourquet
Copy link
Member Author

Ah yes I didn't think about exceptions, will do.
@simonbyrne If you mean the overload srand() do; ... end, the problem is that it's probably never what we would want in a test: the seed is then produced by RandomDevice(), and any hope of reproducibility is lost within the block (I was taught so by Stefan!) That said, it's a bit weird to disallow this, it's probably enough to provide guardsrand() with some doc.

@rfourquet
Copy link
Member Author

In the link of my previous comment, Stefan also said:

[guarding against regressions] is the only thing that automated tests are for and you absolutely do not want random failures – you want to pick some seed that is known to pass and make sure that it continues to pass.

I think this can be addressed with #16924: we can run systematically the whole testsuite by providing a fixed seed (or a set thereof) on the command line, know to pass (I guess this would entail inserting new tests only at the end of test files).

He also conceded:

If a test should always pass regardless of what random values are produced, then it's ok to call it on changing random values but you do want to record what they were for later inspection in case the tests fail

Running the testsuite a second time without providing any seed (selected then at random) allows to test many different random values. The user facing a failure would be asked to provide the seed (printed on the screen) in her bug report.

@rfourquet
Copy link
Member Author

Another alternative: putting independant tests in blocks. This is what is already often done with let blocks, to avoid polluting the global scope with variable names. It is in a way equivalent to putting those independant tests in separate files. This practice could be upgraded to "srand-resiliency" with the following:

newtest() do 
   # here the global RNG is reseeded with its initial seed
   # few related tests
end

If a particular test fails this allows to debug it more easily (i.e. without having to rerun the whole file) as the seed would be known (by #16924).

@tkelman
Copy link
Contributor

tkelman commented Jun 15, 2016

could tie this into a feature of testsets. main reason testsets aren't used for base yet is the default printing is too noisy. a custom testset type to keep the output (and parallel execution) more in line with how the current base tests work should be doable, no one has put in the effort to make it happen yet.

@rfourquet
Copy link
Member Author

Yes I thought also about testests for that "newtest" funcionality, but didn't know about why they aren't used for base, besides the noisy printing. I saw also that it's a bit rigid: the argument of a @testset cannot be any expression, only begin or for.
I cannot make a call on the prefered solution.

@mschauer
Copy link
Contributor

If all you need is to preserve the non-deterministic random state, I used the device

seed = rand(UInt)
srand(123)
...
srand(seed)

before in test/random.jl, but extending the interface DSFMT_state to give access to the state seems to be a good idea anyway.

@rfourquet
Copy link
Member Author

This PR is indeed about formalizing this practice of non-determinism preservation with a simple do-block.

@tkelman
Copy link
Contributor

tkelman commented Jun 15, 2016

I saw also that it's a bit rigid: the argument of a @testset cannot be any expression, only begin or for.

I don't think that has to be set in stone. I guess a question is whether you would really want this scoped RNG seeding feature in contexts other than testing.

@rfourquet
Copy link
Member Author

I don't have in mind a use case of this feature in contexts other than testing. That said, updating the @testset framework and generalizing its usage in base will take some time, so I believe now that we should add this simple guardsrand functionality, and inject it in @testset when appropriate. I consider that the leaking srand in our tests is a (long standing) bug, in particular in test/random.jl: because of few srand(x), which were intended only for a particular subtests, all the random.jl test is always run with the same random numbers (where many of the tests were intended to be run with changing random values). So I vote for fixing this bug now. Furthermore, guardsrand could be used in other testing framework, so it makes sense to decouple it from Base.Test.

@andreasnoack
Copy link
Member

+1 for overloading srand where you'll have to provide a seed, i.e. srand(f::Any, seed::Integer).

@rfourquet
Copy link
Member Author

I guess guardsrand will ever be needed only in random.jl tests, so I will move this function there. I wonder if we should have the general srand(f::Any, rng=GLOBAL_RNG, seed) for all supported seed types with optional RNG, or only srand(f::Any, seed::Integer), which is the only version needed to cover actual Base tests (and extend it if needs arise).

@rfourquet
Copy link
Member Author

I rebased with the following simplification: I merged guardsrand and the srand method taking a thunk into guardsrand, and put this function in the Test module: I can't foresee a use of this outside of testing, and it can be moved in Base when needed. So the functions are:

  1. guardsrand(f) to run f and then restore the state of GLOBAL_RNG as it was before.
  2. guardsrand(f, seed) to do the same but also run srand(seed) before f().

As said above, there are few test files from the "linalg/" folder that I didn't touch, when srand is at the top of the file: with #16924, this is not such a problem as the seeding of the global RNG does not affect anymore its state in other test files. Still, I think this other PR would allow to remove most of these srand.

Note also that it would be possible that some tests start to fail sometimes, if I missed its dependence on a previous srand. I think it's acceptable (the fix would then be to simply insert a new guardsrand more locally (or maybe to set a higher tolerance for float approximation tests).

Will merge in a few days if no objection.

@rfourquet rfourquet merged commit 47fbcc9 into master Oct 8, 2017
@rfourquet rfourquet deleted the rf/srand-tests branch October 8, 2017 12:55
@vtjnash
Copy link
Sponsor Member

vtjnash commented Oct 9, 2017

Is this essentially an existing bug in the test being exposed by this: https://ci.appveyor.com/project/JuliaLang/julia/build/1.0.19809/job/n6q5jgmeti16sy7m

(lots of sprand calls in this file aren't using a fixed seed anymore, the failing one is near

A = spdiagm(rand(5)) + sprandn(5, 5, 0.2) + im*sprandn(5, 5, 0.2)
)

@rfourquet
Copy link
Member Author

It's totally possible, even if I didn't think that this change could cause a method error the first time I saw this failure. If the failure is reproducible, this change is likely to be the cause. To test that, one can run julia runtests.jl sparse/sparse --seed=SEED where SEED is copy pasted from the end of the log, right after "FAILURE" in red (and also test with a couple random seeds to check if it can also succeed reproducibly). An obvious temporary fix to silence the failure would then be to introduce a guardsrand. Unfortunately I will be away from computer till the WE so can't do that myself for now. Also someone who knows the linear algebra code and tests is probably needed to check whether there is a real bug exposed here.

rfourquet added a commit that referenced this pull request Oct 19, 2017
The seed was originally globally fixed in this file, but then locally scoped
as a result of #16940. But it was needed in the "sysv" testset.
rfourquet added a commit that referenced this pull request Oct 19, 2017
The seed was originally globally fixed in this file, but then locally scoped
as a result of #16940. But it was needed in the "sysv" testset.
fredrikekre pushed a commit that referenced this pull request Oct 20, 2017
The seed was originally globally fixed in this file, but then locally scoped
as a result of #16940. But it was needed in the "sysv" testset.
rfourquet added a commit that referenced this pull request Nov 2, 2017
This is a follow-up on the recently introduced `guardsrand` functionality,
first suggested at
#16940 (comment).
Each `testset` block is now implicitly wrapped in a `guardsrand` block, which
is more user-friendly and more systematic (only few users know about
`guardsrand`).

These are essentially two new features:

1) "in": in base, test are run with the global RNG randomly seeded,
   but the seed is printed in case of failure to allow reproducing the
   failure; but even if the failure occurs after many tests, when they
   alter the global RNG state, one has to re-run the whole file, which
   can be time-consuming; with this change, at the beginning of each
   `testset`, the global RNG is re-seeded with its own seed: this
   allows to re-run only the failing `testset`, which can be done
   easily in the REPL (the seeding occurs for each loop in a `testset
   for`; this also allows to re-arrange `testset`s in arbitrary order
   w.r.t. the seeding;

2) "out": a `testset` leaves no tracks of its use of `srand` or `rand`
   (this "feature" should be less and less needed/useful with the
   generalization of the use of `testset` blocks).

   ```
   @testset begin
       srand(123)
       rand()
       @testset for T in (Int, Float64)
           # here is an implicit srand(123), by 1)
           rand()
       end
       rand() # this value will not be affected if the sub-`testset` block
              # above is removed, or if another rand() call is added therein, by 2)
    end
    ```
rfourquet added a commit that referenced this pull request Nov 2, 2017
This is a follow-up on the recently introduced `guardsrand` functionality,
first suggested at
#16940 (comment).
Each `testset` block is now implicitly wrapped in a `guardsrand` block, which
is more user-friendly and more systematic (only few users know about
`guardsrand`).

These are essentially two new features:

1) "in": in base, tests are run with the global RNG randomly seeded,
   but the seed is printed in case of failure to allow reproducing the
   failure; but even if the failure occurs after many tests, when they
   alter the global RNG state, one has to re-run the whole file, which
   can be time-consuming; with this change, at the beginning of each
   `testset`, the global RNG is re-seeded with its own seed: this
   allows to re-run only the failing `testset`, which can be done
   easily in the REPL (the seeding occurs for each loop in a `testset for`;
   this also allows to re-arrange `testset`s in arbitrary order
   w.r.t. the global RNG;

2) "out": a `testset` leaves no tracks of its use of `srand` or `rand`
   (this "feature" should be less and less needed/useful with the
   generalization of the use of `testset` blocks).

Example:
```
@testset begin
   srand(123)
   rand()
   @testset for T in (Int, Float64)
       # here is an implicit srand(123), by 1)
       rand()
   end
   rand() # this value will not be affected if the sub-`testset` block
          # above is removed, or if another rand() call is added therein, by 2)
end
```
rfourquet added a commit to rfourquet/julia that referenced this pull request Dec 16, 2017
This is a follow-up on the recently introduced `guardsrand` functionality,
first suggested at
JuliaLang#16940 (comment).
Each `testset` block is now implicitly wrapped in a `guardsrand` block, which
is more user-friendly and more systematic (only few users know about
`guardsrand`).

These are essentially two new features:

1) "in": in base, tests are run with the global RNG randomly seeded,
   but the seed is printed in case of failure to allow reproducing the
   failure; but even if the failure occurs after many tests, when they
   alter the global RNG state, one has to re-run the whole file, which
   can be time-consuming; with this change, at the beginning of each
   `testset`, the global RNG is re-seeded with its own seed: this
   allows to re-run only the failing `testset`, which can be done
   easily in the REPL (the seeding occurs for each loop in a `testset for`;
   this also allows to re-arrange `testset`s in arbitrary order
   w.r.t. the global RNG;

2) "out": a `testset` leaves no tracks of its use of `srand` or `rand`
   (this "feature" should be less and less needed/useful with the
   generalization of the use of `testset` blocks).

Example:
```
@testset begin
   srand(123)
   rand()
   @testset for T in (Int, Float64)
       # here is an implicit srand(123), by 1)
       rand()
   end
   rand() # this value will not be affected if the sub-`testset` block
          # above is removed, or if another rand() call is added therein, by 2)
end
```

Note that guardsrand can't be used directly, as then the testset's body is
wrapped in a function, which causes problem with overwriting loop variable
("outer"), using `using`, defining new methods, etc. So we need to duplicate
guardsrand's logic.
rfourquet added a commit that referenced this pull request Dec 16, 2017
This is a follow-up on the recently introduced `guardsrand` functionality,
first suggested at
#16940 (comment).
Each `testset` block is now implicitly wrapped in a `guardsrand` block, which
is more user-friendly and more systematic (only few users know about
`guardsrand`).

These are essentially two new features:

1) "in": in base, tests are run with the global RNG randomly seeded,
   but the seed is printed in case of failure to allow reproducing the
   failure; but even if the failure occurs after many tests, when they
   alter the global RNG state, one has to re-run the whole file, which
   can be time-consuming; with this change, at the beginning of each
   `testset`, the global RNG is re-seeded with its own seed: this
   allows to re-run only the failing `testset`, which can be done
   easily in the REPL (the seeding occurs for each loop in a `testset for`;
   this also allows to re-arrange `testset`s in arbitrary order
   w.r.t. the global RNG;

2) "out": a `testset` leaves no tracks of its use of `srand` or `rand`
   (this "feature" should be less and less needed/useful with the
   generalization of the use of `testset` blocks).

Example:
```
@testset begin
   srand(123)
   rand()
   @testset for T in (Int, Float64)
       # here is an implicit srand(123), by 1)
       rand()
   end
   rand() # this value will not be affected if the sub-`testset` block
          # above is removed, or if another rand() call is added therein, by 2)
end
```

Note that guardsrand can't be used directly, as then the testset's body is
wrapped in a function, which causes problem with overwriting loop variable
("outer"), using `using`, defining new methods, etc. So we need to duplicate
guardsrand's logic.
rfourquet added a commit that referenced this pull request Dec 16, 2017
This is a follow-up on the recently introduced `guardsrand` functionality,
first suggested at
#16940 (comment).
Each `testset` block is now implicitly wrapped in a `guardsrand` block, which
is more user-friendly and more systematic (only few users know about
`guardsrand`).

These are essentially two new features:

1) "in": in base, tests are run with the global RNG randomly seeded,
   but the seed is printed in case of failure to allow reproducing the
   failure; but even if the failure occurs after many tests, when they
   alter the global RNG state, one has to re-run the whole file, which
   can be time-consuming; with this change, at the beginning of each
   `testset`, the global RNG is re-seeded with its own seed: this
   allows to re-run only the failing `testset`, which can be done
   easily in the REPL (the seeding occurs for each loop in a `testset for`;
   this also allows to re-arrange `testset`s in arbitrary order
   w.r.t. the global RNG;

2) "out": a `testset` leaves no tracks of its use of `srand` or `rand`
   (this "feature" should be less and less needed/useful with the
   generalization of the use of `testset` blocks).

Example:
```
@testset begin
   srand(123)
   rand()
   @testset for T in (Int, Float64)
       # here is an implicit srand(123), by 1)
       rand()
   end
   rand() # this value will not be affected if the sub-`testset` block
          # above is removed, or if another rand() call is added therein, by 2)
end
```

Note that guardsrand can't be used directly, as then the testset's body is
wrapped in a function, which causes problem with overwriting loop variable
("outer"), using `using`, defining new methods, etc. So we need to duplicate
guardsrand's logic.
rfourquet added a commit that referenced this pull request Dec 16, 2017
This is a follow-up on the recently introduced `guardsrand` functionality,
first suggested at
#16940 (comment).
Each `testset` block is now implicitly wrapped in a `guardsrand` block, which
is more user-friendly and more systematic (only few users know about
`guardsrand`).

These are essentially two new features:

1) "in": in base, tests are run with the global RNG randomly seeded,
   but the seed is printed in case of failure to allow reproducing the
   failure; but even if the failure occurs after many tests, when they
   alter the global RNG state, one has to re-run the whole file, which
   can be time-consuming; with this change, at the beginning of each
   `testset`, the global RNG is re-seeded with its own seed: this
   allows to re-run only the failing `testset`, which can be done
   easily in the REPL (the seeding occurs for each loop in a `testset for`;
   this also allows to re-arrange `testset`s in arbitrary order
   w.r.t. the global RNG;

2) "out": a `testset` leaves no tracks of its use of `srand` or `rand`
   (this "feature" should be less and less needed/useful with the
   generalization of the use of `testset` blocks).

Example:
```
@testset begin
   srand(123)
   rand()
   @testset for T in (Int, Float64)
       # here is an implicit srand(123), by 1)
       rand()
   end
   rand() # this value will not be affected if the sub-`testset` block
          # above is removed, or if another rand() call is added therein, by 2)
end
```

Note that guardsrand can't be used directly, as then the testset's body is
wrapped in a function, which causes problem with overwriting loop variable
("outer"), using `using`, defining new methods, etc. So we need to duplicate
guardsrand's logic.
rfourquet added a commit that referenced this pull request Dec 17, 2017
This is a follow-up on the recently introduced `guardsrand` functionality,
first suggested at
#16940 (comment).
Each `testset` block is now implicitly wrapped in a `guardsrand` block, which
is more user-friendly and more systematic (only few users know about
`guardsrand`).

These are essentially two new features:

1) "in": in base, tests are run with the global RNG randomly seeded,
   but the seed is printed in case of failure to allow reproducing the
   failure; but even if the failure occurs after many tests, when they
   alter the global RNG state, one has to re-run the whole file, which
   can be time-consuming; with this change, at the beginning of each
   `testset`, the global RNG is re-seeded with its own seed: this
   allows to re-run only the failing `testset`, which can be done
   easily in the REPL (the seeding occurs for each loop in a `testset for`;
   this also allows to re-arrange `testset`s in arbitrary order
   w.r.t. the global RNG;

2) "out": a `testset` leaves no tracks of its use of `srand` or `rand`
   (this "feature" should be less and less needed/useful with the
   generalization of the use of `testset` blocks).

Example:
```
@testset begin
   srand(123)
   rand()
   @testset for T in (Int, Float64)
       # here is an implicit srand(123), by 1)
       rand()
   end
   rand() # this value will not be affected if the sub-`testset` block
          # above is removed, or if another rand() call is added therein, by 2)
end
```

Note that guardsrand can't be used directly, as then the testset's body is
wrapped in a function, which causes problem with overwriting loop variable
("outer"), using `using`, defining new methods, etc. So we need to duplicate
guardsrand's logic.
rfourquet added a commit to rfourquet/julia that referenced this pull request Dec 17, 2017
This is a follow-up on the recently introduced `guardsrand` functionality,
first suggested at
JuliaLang#16940 (comment).
Each `testset` block is now implicitly wrapped in a `guardsrand` block, which
is more user-friendly and more systematic (only few users know about
`guardsrand`).

These are essentially two new features:

1) "in": in base, tests are run with the global RNG randomly seeded,
   but the seed is printed in case of failure to allow reproducing the
   failure; but even if the failure occurs after many tests, when they
   alter the global RNG state, one has to re-run the whole file, which
   can be time-consuming; with this change, at the beginning of each
   `testset`, the global RNG is re-seeded with its own seed: this
   allows to re-run only the failing `testset`, which can be done
   easily in the REPL (the seeding occurs for each loop in a `testset for`;
   this also allows to re-arrange `testset`s in arbitrary order
   w.r.t. the global RNG;

2) "out": a `testset` leaves no tracks of its use of `srand` or `rand`
   (this "feature" should be less and less needed/useful with the
   generalization of the use of `testset` blocks).

Example:
```
@testset begin
   srand(123)
   rand()
   @testset for T in (Int, Float64)
       # here is an implicit srand(123), by 1)
       rand()
   end
   rand() # this value will not be affected if the sub-`testset` block
          # above is removed, or if another rand() call is added therein, by 2)
end
```

Note that guardsrand can't be used directly, as then the testset's body is
wrapped in a function, which causes problem with overwriting loop variable
("outer"), using `using`, defining new methods, etc. So we need to duplicate
guardsrand's logic.
rfourquet added a commit that referenced this pull request Dec 17, 2017
[ci skip]
[av skip]
[bsd skip]

This is a follow-up on the recently introduced `guardsrand` functionality,
first suggested at
#16940 (comment).
Each `testset` block is now implicitly wrapped in a `guardsrand` block, which
is more user-friendly and more systematic (only few users know about
`guardsrand`).

These are essentially two new features:

1) "in": in base, tests are run with the global RNG randomly seeded,
   but the seed is printed in case of failure to allow reproducing the
   failure; but even if the failure occurs after many tests, when they
   alter the global RNG state, one has to re-run the whole file, which
   can be time-consuming; with this change, at the beginning of each
   `testset`, the global RNG is re-seeded with its own seed: this
   allows to re-run only the failing `testset`, which can be done
   easily in the REPL (the seeding occurs for each loop in a `testset for`;
   this also allows to re-arrange `testset`s in arbitrary order
   w.r.t. the global RNG;

2) "out": a `testset` leaves no tracks of its use of `srand` or `rand`
   (this "feature" should be less and less needed/useful with the
   generalization of the use of `testset` blocks).

Example:
```
@testset begin
   srand(123)
   rand()
   @testset for T in (Int, Float64)
       # here is an implicit srand(123), by 1)
       rand()
   end
   rand() # this value will not be affected if the sub-`testset` block
          # above is removed, or if another rand() call is added therein, by 2)
end
```

Note that guardsrand can't be used directly, as then the testset's body is
wrapped in a function, which causes problem with overwriting loop variable
("outer"), using `using`, defining new methods, etc. So we need to duplicate
guardsrand's logic.
rfourquet added a commit that referenced this pull request Dec 17, 2017
This is a follow-up on the recently introduced `guardsrand` functionality,
first suggested at
#16940 (comment).
Each `testset` block is now implicitly wrapped in a `guardsrand` block, which
is more user-friendly and more systematic (only few users know about
`guardsrand`).

These are essentially two new features:

1) "in": in base, tests are run with the global RNG randomly seeded,
   but the seed is printed in case of failure to allow reproducing the
   failure; but even if the failure occurs after many tests, when they
   alter the global RNG state, one has to re-run the whole file, which
   can be time-consuming; with this change, at the beginning of each
   `testset`, the global RNG is re-seeded with its own seed: this
   allows to re-run only the failing `testset`, which can be done
   easily in the REPL (the seeding occurs for each loop in a `testset for`;
   this also allows to re-arrange `testset`s in arbitrary order
   w.r.t. the global RNG;

2) "out": a `testset` leaves no tracks of its use of `srand` or `rand`
   (this "feature" should be less and less needed/useful with the
   generalization of the use of `testset` blocks).

Example:
```
@testset begin
   srand(123)
   rand()
   @testset for T in (Int, Float64)
       # here is an implicit srand(123), by 1)
       rand()
   end
   rand() # this value will not be affected if the sub-`testset` block
          # above is removed, or if another rand() call is added therein, by 2)
end
```

Note that guardsrand can't be used directly, as then the testset's body is
wrapped in a function, which causes problem with overwriting loop variable
("outer"), using `using`, defining new methods, etc. So we need to duplicate
guardsrand's logic.
andreasnoack pushed a commit to JuliaLinearAlgebra/Arpack.jl that referenced this pull request May 26, 2018
This is a follow-up on the recently introduced `guardsrand` functionality,
first suggested at
JuliaLang/julia#16940 (comment).
Each `testset` block is now implicitly wrapped in a `guardsrand` block, which
is more user-friendly and more systematic (only few users know about
`guardsrand`).

These are essentially two new features:

1) "in": in base, tests are run with the global RNG randomly seeded,
   but the seed is printed in case of failure to allow reproducing the
   failure; but even if the failure occurs after many tests, when they
   alter the global RNG state, one has to re-run the whole file, which
   can be time-consuming; with this change, at the beginning of each
   `testset`, the global RNG is re-seeded with its own seed: this
   allows to re-run only the failing `testset`, which can be done
   easily in the REPL (the seeding occurs for each loop in a `testset for`;
   this also allows to re-arrange `testset`s in arbitrary order
   w.r.t. the global RNG;

2) "out": a `testset` leaves no tracks of its use of `srand` or `rand`
   (this "feature" should be less and less needed/useful with the
   generalization of the use of `testset` blocks).

Example:
```
@testset begin
   srand(123)
   rand()
   @testset for T in (Int, Float64)
       # here is an implicit srand(123), by 1)
       rand()
   end
   rand() # this value will not be affected if the sub-`testset` block
          # above is removed, or if another rand() call is added therein, by 2)
end
```

Note that guardsrand can't be used directly, as then the testset's body is
wrapped in a function, which causes problem with overwriting loop variable
("outer"), using `using`, defining new methods, etc. So we need to duplicate
guardsrand's logic.
KristofferC pushed a commit to JuliaSparse/SuiteSparse.jl that referenced this pull request May 10, 2021
This is a follow-up on the recently introduced `guardsrand` functionality,
first suggested at
JuliaLang/julia#16940 (comment).
Each `testset` block is now implicitly wrapped in a `guardsrand` block, which
is more user-friendly and more systematic (only few users know about
`guardsrand`).

These are essentially two new features:

1) "in": in base, tests are run with the global RNG randomly seeded,
   but the seed is printed in case of failure to allow reproducing the
   failure; but even if the failure occurs after many tests, when they
   alter the global RNG state, one has to re-run the whole file, which
   can be time-consuming; with this change, at the beginning of each
   `testset`, the global RNG is re-seeded with its own seed: this
   allows to re-run only the failing `testset`, which can be done
   easily in the REPL (the seeding occurs for each loop in a `testset for`;
   this also allows to re-arrange `testset`s in arbitrary order
   w.r.t. the global RNG;

2) "out": a `testset` leaves no tracks of its use of `srand` or `rand`
   (this "feature" should be less and less needed/useful with the
   generalization of the use of `testset` blocks).

Example:
```
@testset begin
   srand(123)
   rand()
   @testset for T in (Int, Float64)
       # here is an implicit srand(123), by 1)
       rand()
   end
   rand() # this value will not be affected if the sub-`testset` block
          # above is removed, or if another rand() call is added therein, by 2)
end
```

Note that guardsrand can't be used directly, as then the testset's body is
wrapped in a function, which causes problem with overwriting loop variable
("outer"), using `using`, defining new methods, etc. So we need to duplicate
guardsrand's logic.
KristofferC pushed a commit to JuliaSparse/SuiteSparse.jl that referenced this pull request May 10, 2021
This is a follow-up on the recently introduced `guardsrand` functionality,
first suggested at
JuliaLang/julia#16940 (comment).
Each `testset` block is now implicitly wrapped in a `guardsrand` block, which
is more user-friendly and more systematic (only few users know about
`guardsrand`).

These are essentially two new features:

1) "in": in base, tests are run with the global RNG randomly seeded,
   but the seed is printed in case of failure to allow reproducing the
   failure; but even if the failure occurs after many tests, when they
   alter the global RNG state, one has to re-run the whole file, which
   can be time-consuming; with this change, at the beginning of each
   `testset`, the global RNG is re-seeded with its own seed: this
   allows to re-run only the failing `testset`, which can be done
   easily in the REPL (the seeding occurs for each loop in a `testset for`;
   this also allows to re-arrange `testset`s in arbitrary order
   w.r.t. the global RNG;

2) "out": a `testset` leaves no tracks of its use of `srand` or `rand`
   (this "feature" should be less and less needed/useful with the
   generalization of the use of `testset` blocks).

Example:
```
@testset begin
   srand(123)
   rand()
   @testset for T in (Int, Float64)
       # here is an implicit srand(123), by 1)
       rand()
   end
   rand() # this value will not be affected if the sub-`testset` block
          # above is removed, or if another rand() call is added therein, by 2)
end
```

Note that guardsrand can't be used directly, as then the testset's body is
wrapped in a function, which causes problem with overwriting loop variable
("outer"), using `using`, defining new methods, etc. So we need to duplicate
guardsrand's logic.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
randomness Random number generation and the Random stdlib
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants