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

Utilise RST conversion of spec, generate runtime special cases tests #104

Merged
merged 63 commits into from
Mar 22, 2022

Conversation

honno
Copy link
Member

@honno honno commented Feb 28, 2022

Ok, this ones a biggie. It's not finished yet, but thought you'd like to take a look @asmeurer.

The first big thing is making use of the RST spec (data-apis/array-api#343). This is achieved by adding the spec repo array-api as a git submodule inside of our array-api-tests/ folder, adding the signatures package to the Python path, and extracting all the function objects (or class in the case of the array object).

(I tried not adding the signatures package to the Python path, as it's rather unseemly and potentially problematic to users, but I struggled trying to parse the functions/methods with the nifty importlib utils due to relative imports.)

Next we have the special case tests.

  • As we're resolving Avoid using boolean array indexing in special case tests #94, I've replaced using boolean masks to filter away irrelevant array elements, with filter functions that use the scalar (i.e. float) of the respect array element. These are constructed in parse_cond(). Results are now checked against the scalar too, with a result-checker function constructed in parse_result().
    • Much of your regex exists in these condition/check-result "factories".
  • For the special cases in binary functions, I've generalised how we parse every condition in a special case
    • No more regexes for each arrangement e.g. TWO_ARGS_EQUAL, TWO_ARGS_EQUAL__LESS, etc. 🎉
  • If we can't parse something, we raise a warning most times. This should tell us if parse_cond()/parse_result()/something else needs updating to cover any new special cases with unforeseen properties.
  • Special cases are represented in dataclasses, and parametrized with generalised test methods, enabling runtime tests as opposed to having statically generated tests.
  • I was trying running all special case tests at once per function, but will return to the one-case one-test pattern.

I still have a fair bit to do, but I'm pretty happy with the architecture now. I will probably touch the signature tests or module stubbing in a later PR.

Note right now there's a bug with the conditions giving false-negatives/postives, due to some pass-by-reference problems when I defined local functions in loops 😅

@asmeurer
Copy link
Member

(I tried not adding the signatures package to the Python path, as it's rather unseemly and potentially problematic to users, but I struggled trying to parse the functions/methods with the nifty importlib utils due to relative imports.)

This seems fine. If you're worried about name conflicts we could rename it to something more meaningful like array_api_signatures.

@asmeurer
Copy link
Member

This looks good so far. Some thoughts:

  • I guess you eliminated the need for the regex module?
  • As long as we are cleaning up the regular expressions, it might be a good idea to replace numbered groups with named groups.
  • A seems like a lot of the code in test_special_cases could be simplified with functools.partial. Although I'm also wondering if so many levels of indirection are actually necessary in the first place.
  • I typically use UPPERCASE for compiled regex objects. I'm actually unsure how common this practice is, though.
  • We should remove the duplication in the function_stubs submodule. I think we can replace the whole thing with a single file using module-level __getattr__. Or we could just remove it entirely and require the use of signatures to access the stubs.
  • It might be a good idea to have some (really fast to run) meta/smoke tests that we can add to the main array-api repo CI to make sure the signatures submodule doesn't break on us.
  • It would be helpful to have more comments in the code explaining what is going on, and short docstrings for all the internal helper functions.

array_api_tests/stubs.py Outdated Show resolved Hide resolved
raise ValueParseError(inline_code)


r_not = re.compile("not (?:equal to )?(.+)")
Copy link
Member

Choose a reason for hiding this comment

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

It's good to always use make regular expressions raw strings, even if they don't have backslashes. You don't want to add a backslash escape later only to not realize the string isn't raw.

@asmeurer
Copy link
Member

If we can't parse something, we raise a warning most times. This should tell us if parse_cond()/parse_result()/something else needs updating to cover any new special cases with unforeseen properties.

Just to be sure, this should already be the case with the existing generate_stubs.py, no? We definitely want to fail if we come across anything we don't recognize.

eq = make_eq(i1)
return eq(i2)

else:
Copy link
Member

Choose a reason for hiding this comment

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

Might be a good idea to code these more defensively like

if value_sign == '+':
    ...
elif value_sign == '-':
    ...
else:
    raise ValueParseError

Copy link
Member Author

@honno honno Mar 1, 2022

Choose a reason for hiding this comment

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

Hmm, I believe the regex match should guarantee that value_sign is only "", "-" or "+". A raise here does work well as a sanity check tho.

if m := r_case.match(line):
case_str = m.group(1)
else:
warn(f"line not machine-readable: '{line}'")
Copy link
Member

Choose a reason for hiding this comment

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

We really want this to be an error, not a warning, I think.

Copy link
Member

Choose a reason for hiding this comment

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

And even if we didn't want this to be an error, I would avoid using warnings. pytest is just going to list warnings at the end, but modules like NumPy already emit dozens of warnings when running the tests, which can be ignored, and any legitimate warning would be completely lost.

Copy link
Member Author

Choose a reason for hiding this comment

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

I'd rather not raise an error here as it's not critical (unlike the assertions I make in stubs.py), so would unnecessarily halt use of the test suite for the myriad of reasons a user could have an out-of-sync array-api submodule.

I agree warnings are pretty awkward in practice, but I still think they're the only appropriate choice we have. I was actually using this in setup.cfg when developing all the parsing code paths:

[tool:pytest]
filterwarnings =
    ignore::ImportWarning
    ignore::RuntimeWarning
    ignore::DeprecationWarning

@asmeurer
Copy link
Member

It might be a good idea to have some (really fast to run) meta/smoke tests that we can add to the main array-api repo CI to make sure the signatures submodule doesn't break on us.

Actually, I just realized the (git) submodule breaks the ability to vendor array_api_tests. I think we can make vendoring possible by allowing copying signatures into array_api_tests as a (Python) submodule. Or we can just copy it manually and commit it, similar to what generate_stubs.py does.

Either way, it makes sense to keep .function_stubs as the primary way to access the stubs.

@honno
Copy link
Member Author

honno commented Mar 1, 2022

I guess you eliminated the need for the regex module?

Yep, I was initially using branch resets like you did, but ended up with a solution that doesn't need it.

As long as we are cleaning up the regular expressions, it might be a good idea to replace numbered groups with named groups.

I did not know this was a thing, very nifty! Will explore using them. I'll still mull them over, but unfortunately for now named groups screw up a few instances where I re-use patterns inside of other patterns.

A seems like a lot of the code in test_special_cases could be simplified with functools.partial. Although I'm also wondering if so many levels of indirection are actually necessary in the first place.

Ahh partial seems problematic for the most part, given the edge cases that need handling and the non-obvious variable names i.e. binary ops in operator usually have a finicky (__a, __b, /) signatures. Generally there might be room to reduce the indirection.

I typically use UPPERCASE for compiled regex objects. I'm actually unsure how common this practice is, though.

Hmm, I've seen uppercase elsewhere, and re_*/r_* too. I like r_* but not religious about it.

We should remove the duplication in the function_stubs submodule. I think we can replace the whole thing with a single file using module-level __getattr__. Or we could just remove it entirely and require the use of signatures to access the stubs.

I'll have a quick look at replacing the submodule, but as I'm not tackling module stubbing/signature tests in this PR I won't prioritise this just yet.

It might be a good idea to have some (really fast to run) meta/smoke tests that we can add to the main array-api repo CI to make sure the signatures submodule doesn't break on us.

I do have a few hard, non-test assertions for all of this, but definitely something I can review.

Actually, I just realized the (git) submodule breaks the ability to vendor array_api_tests. I think we can make vendoring possible by allowing copying signatures into array_api_tests as a (Python) submodule. Or we can just copy it manually and commit it, similar to what generate_stubs.py does.

Hmm, as I'm sure you'd agree it'd be ideal if we didn't hardcode things from a maintenance perspective. I guess the question is if it's too annoying to ask users to pull and deal with an array-api submodule? I'd rather err on the side of maintainability, and maybe change tact if folk complain.

It would be helpful to have more comments in the code explaining what is going on, and short docstrings for all the internal helper functions.

Yep, once I stabilise things 😅

If we can't parse something, we raise a warning most times. This should tell us if parse_cond()/parse_result()/something else needs updating to cover any new special cases with unforeseen properties.

Just to be sure, this should already be the case with the existing generate_stubs.py, no?

Yep I believe generate_stubs.py communicates somehow when something can't be parsed.

@asmeurer
Copy link
Member

asmeurer commented Mar 1, 2022

Hmm, as I'm sure you'd agree it'd be ideal if we didn't hardcode things from a maintenance perspective. I guess the question is if it's too annoying to ask users to pull and deal with an array-api submodule? I'd rather err on the side of maintainability, and maybe change tact if folk complain.

I don't think we should require vendored users to use a submodule. If we don't commit the signature files, we need a way for users to do it, like a script that packages a standalone vendor module that people can use, meaning they have to run that each time they update. So it's a question of trading some maintenance upside for us with some usability for end-users. OTOH maybe this is just a reason why we should upload a package so that people who vendor can copy the from that.

@honno
Copy link
Member Author

honno commented Mar 2, 2022

Hmm, as I'm sure you'd agree it'd be ideal if we didn't hardcode things from a maintenance perspective. I guess the question is if it's too annoying to ask users to pull and deal with an array-api submodule? I'd rather err on the side of maintainability, and maybe change tact if folk complain.

I don't think we should require vendored users to use a submodule. If we don't commit the signature files, we need a way for users to do it, like a script that packages a standalone vendor module that people can use, meaning they have to run that each time they update. So it's a question of trading some maintenance upside for us with some usability for end-users. OTOH maybe this is just a reason why we should upload a package so that people who vendor can copy the from that.

Ok yeah I see. What do we mean when we say vendored though? Like the only practical use we're seeing right now is pulling the repo and running it in CI, where the submodule requirement is only a minor inconvenience. If a different use case arises then it may be appropriate to support it only then.

Anywho, I think if we hard copy signatures, I'd want to set up a pre-commit script that checks our own vendored signatures is synced with a version we specify somewhere. This PR is so big already that I'd want to do all that in another PR at once.

@honno
Copy link
Member Author

honno commented Mar 2, 2022

#40 might be pretty tricky. It seems there's a fair few existing special case tests that don't generate that many relevant examples already, so it could be apt to instead generate a single minimal reproducer per special case for now, as generating relevant Hypothesis arrays is tricky enough for a PR in its own right. Will explore this more later anywho.

Special cases in this PR that fail Hypothesis' health check for unmodified array strategies
test_unary[abs-x_i == -0 -> +0]
test_unary[acos-x_i == 1 -> +0]
test_unary[acosh-x_i == 1 -> +0]
test_unary[acosh-x_i == +infinity -> +infinity]
test_unary[asin-x_i == +0 -> +0]
test_unary[asin-x_i == -0 -> -0]
test_unary[asinh-x_i == -0 -> -0]
test_unary[asinh-x_i == -infinity -> -infinity]
test_unary[atan-x_i == +0 -> +0]
test_unary[atan-x_i == -0 -> -0]
test_unary[atanh-x_i == -1 -> -infinity]
test_unary[atanh-x_i == +1 -> +infinity]
test_unary[atanh-x_i == -0 -> -0]
test_unary[ceil-x_i == -0 -> -0]
test_unary[cos-x_i == -0 -> 1]
test_unary[cos-x_i == +infinity -> NaN]
test_unary[cosh-x_i == -0 -> 1]
test_unary[exp-x_i == -0 -> 1]
test_unary[exp-x_i == -infinity -> +0]
test_unary[expm1-x_i == -0 -> -0]
test_unary[expm1-x_i == +infinity -> +infinity]
test_unary[expm1-x_i == -infinity -> -1]
test_unary[floor-x_i == +infinity -> +infinity]
test_unary[floor-x_i == -infinity -> -infinity]
test_unary[floor-x_i == -0 -> -0]
test_unary[log-x_i == 1 -> +0]
test_unary[log1p-x_i == -1 -> -infinity]
test_unary[log1p-x_i == -0 -> -0]
test_unary[log1p-x_i == +0 -> +0]
test_unary[log2-x_i == 1 -> +0]
test_unary[log10-x_i == 1 -> +0]
test_unary[round-x_i == +infinity -> +infinity]
test_unary[round-x_i == -0 -> -0]
test_unary[sin-x_i == -0 -> -0]
test_unary[sinh-x_i == -0 -> -0]
test_unary[sinh-x_i == +infinity -> +infinity]
test_unary[sinh-x_i == -infinity -> -infinity]
test_unary[sqrt-x_i == -0 -> -0]
test_unary[sqrt-x_i == +infinity -> +infinity]
test_unary[tan-x_i == +0 -> +0]
test_unary[tan-x_i == -0 -> -0]
test_unary[tanh-x_i == +0 -> +0]
test_unary[tanh-x_i == -0 -> -0]
test_unary[tanh-x_i == +infinity -> +1]
test_unary[trunc-x_i == -0 -> -0]
test_binary[add-x1_i == +infinity and x2_i == -infinity -> NaN]
test_binary[add-x1_i == -infinity and x2_i == +infinity -> NaN]
test_binary[add-x1_i == +infinity and x2_i == +infinity -> +infinity]
test_binary[add-x1_i == -infinity and x2_i == -infinity -> -infinity]
test_binary[add-x1_i == +infinity and isfinite(x2_i) -> +infinity]
test_binary[add-x1_i == -infinity and isfinite(x2_i) -> -infinity]
test_binary[add-isfinite(x1_i) and x2_i == -infinity -> -infinity]
test_binary[add-x1_i == -0 and x2_i == -0 -> -0]
test_binary[add-x1_i == -0 and x2_i == +0 -> +0]
test_binary[add-x1_i == +0 and x2_i == -0 -> +0]
test_binary[add-x1_i == +0 or x1_i == -0 and isfinite(x2_i) and x2_i != 0 -> x2_i]
test_binary[add-isfinite(x1_i) and x1_i != 0 and x2_i == +0 or x2_i == -0 -> x1_i]
test_binary[add-isfinite(x1_i) and x1_i != 0 and x2_i == -x1_i -> +0]
test_binary[atan2-x1_i > 0 and x2_i == +0 -> roughly +pi/2]
test_binary[atan2-x1_i > 0 and x2_i == -0 -> roughly +pi/2]
test_binary[atan2-x1_i == +0 and x2_i > 0 -> +0]
test_binary[atan2-x1_i == +0 and x2_i == -0 -> roughly +pi]
test_binary[atan2-x1_i == +0 and x2_i < 0 -> roughly +pi]
test_binary[atan2-x1_i == -0 and x2_i > 0 -> -0]
test_binary[atan2-x1_i == -0 and x2_i == +0 -> -0]
test_binary[atan2-x1_i == -0 and x2_i == -0 -> roughly -pi]
test_binary[atan2-x1_i == -0 and x2_i < 0 -> roughly -pi]
test_binary[atan2-x1_i < 0 and x2_i == +0 -> roughly -pi/2]
test_binary[atan2-x1_i < 0 and x2_i == -0 -> roughly -pi/2]
test_binary[atan2-x1_i > 0 and isfinite(x1_i) and x2_i == +infinity -> +0]
test_binary[atan2-x1_i > 0 and isfinite(x1_i) and x2_i == -infinity -> roughly +pi]
test_binary[atan2-x1_i < 0 and isfinite(x1_i) and x2_i == +infinity -> -0]
test_binary[atan2-x1_i < 0 and isfinite(x1_i) and x2_i == -infinity -> roughly -pi]
test_binary[atan2-x1_i == -infinity and isfinite(x2_i) -> roughly -pi/2]
test_binary[atan2-x1_i == +infinity and x2_i == +infinity -> roughly +pi/4]
test_binary[atan2-x1_i == +infinity and x2_i == -infinity -> roughly +3pi/4]
test_binary[atan2-x1_i == -infinity and x2_i == +infinity -> roughly -pi/4]
test_binary[atan2-x1_i == -infinity and x2_i == -infinity -> roughly -3pi/4]
test_binary[divide-x1_i == +infinity or x1_i == -infinity and x2_i == +infinity or x2_i == -infinity -> NaN]
test_binary[divide-x1_i == +0 and x2_i > 0 -> +0]
test_binary[divide-x1_i == -0 and x2_i > 0 -> -0]
test_binary[divide-x1_i == +0 and x2_i < 0 -> -0]
test_binary[divide-x1_i == -0 and x2_i < 0 -> +0]
test_binary[divide-x1_i > 0 and x2_i == +0 -> +infinity]
test_binary[divide-x1_i > 0 and x2_i == -0 -> -infinity]
test_binary[divide-x1_i < 0 and x2_i == +0 -> -infinity]
test_binary[divide-x1_i < 0 and x2_i == -0 -> +infinity]
test_binary[divide-x1_i == +infinity and isfinite(x2_i) and x2_i > 0 -> +infinity]
test_binary[divide-x1_i == -infinity and isfinite(x2_i) and x2_i > 0 -> -infinity]
test_binary[divide-isfinite(x1_i) and x1_i > 0 and x2_i == +infinity -> +0]
test_binary[divide-isfinite(x1_i) and x1_i > 0 and x2_i == -infinity -> -0]
test_binary[divide-isfinite(x1_i) and x1_i < 0 and x2_i == +infinity -> -0]
test_binary[floor_divide-x1_i == +infinity or x1_i == -infinity and x2_i == +infinity or x2_i == -infinity -> NaN]
test_binary[floor_divide-x1_i == +0 and x2_i > 0 -> +0]
test_binary[floor_divide-x1_i == -0 and x2_i > 0 -> -0]
test_binary[floor_divide-x1_i == +0 and x2_i < 0 -> -0]
test_binary[floor_divide-x1_i == -0 and x2_i < 0 -> +0]
test_binary[floor_divide-x1_i > 0 and x2_i == +0 -> +infinity]
test_binary[floor_divide-x1_i > 0 and x2_i == -0 -> -infinity]
test_binary[floor_divide-x1_i < 0 and x2_i == +0 -> -infinity]
test_binary[floor_divide-x1_i < 0 and x2_i == -0 -> +infinity]
test_binary[floor_divide-x1_i == +infinity and isfinite(x2_i) and x2_i > 0 -> +infinity]
test_binary[floor_divide-x1_i == +infinity and isfinite(x2_i) and x2_i < 0 -> -infinity]
test_binary[floor_divide-x1_i == -infinity and isfinite(x2_i) and x2_i > 0 -> -infinity]
test_binary[floor_divide-x1_i == -infinity and isfinite(x2_i) and x2_i < 0 -> +infinity]
test_binary[floor_divide-isfinite(x1_i) and x1_i > 0 and x2_i == +infinity -> +0]
test_binary[floor_divide-isfinite(x1_i) and x1_i > 0 and x2_i == -infinity -> -0]
test_binary[floor_divide-isfinite(x1_i) and x1_i < 0 and x2_i == +infinity -> -0]
test_binary[floor_divide-isfinite(x1_i) and x1_i < 0 and x2_i == -infinity -> +0]
test_binary[multiply-x1_i == +infinity or x1_i == -infinity and x2_i == +0 or x2_i == -0 -> NaN]
test_binary[multiply-x1_i == +0 or x1_i == -0 and x2_i == +infinity or x2_i == -infinity -> NaN]
test_binary[pow-x2_i == -0 -> 1]
test_binary[pow-x1_i == NaN and not (x2_i == 0) -> NaN]
test_binary[pow-abs(x1_i) > 1 and x2_i == -infinity -> +0]
test_binary[pow-abs(x1_i) == 1 and x2_i == +infinity -> 1]
test_binary[pow-abs(x1_i) == 1 and x2_i == -infinity -> 1]
test_binary[pow-x1_i == 1 and not (x2_i == NaN) -> 1]
test_binary[pow-abs(x1_i) < 1 and x2_i == -infinity -> +infinity]
test_binary[pow-x1_i == +infinity and x2_i > 0 -> +infinity]
test_binary[pow-x1_i == +infinity and x2_i < 0 -> +0]
test_binary[pow-x1_i == -infinity and x2_i > 0 and x2_i.is_integer() and x2_i % 2 == 1 -> -infinity]
test_binary[pow-x1_i == -infinity and x2_i > 0 and not (x2_i.is_integer() and x2_i % 2 == 1) -> +infinity]
test_binary[pow-x1_i == -infinity and x2_i < 0 and x2_i.is_integer() and x2_i % 2 == 1 -> -0]
test_binary[pow-x1_i == +0 and x2_i > 0 -> +0]
test_binary[pow-x1_i == +0 and x2_i < 0 -> +infinity]
test_binary[pow-x1_i == -0 and x2_i > 0 and x2_i.is_integer() and x2_i % 2 == 1 -> -0]
test_binary[pow-x1_i == -0 and x2_i > 0 and not (x2_i.is_integer() and x2_i % 2 == 1) -> +0]
test_binary[pow-x1_i == -0 and x2_i < 0 and x2_i.is_integer() and x2_i % 2 == 1 -> -infinity]
test_binary[pow-x1_i == -0 and x2_i < 0 and not (x2_i.is_integer() and x2_i % 2 == 1) -> +infinity]

07/03 update: Generating good examples for unary functions—and binary functions w/ one "sub" case per x1/x2—is fairly simple and works great. I have however found it difficult to get the right strategies when their are 2+ cases per array i.e. x2_i is foo, and x2_i is bar.

I discussed this particular issue up in the internal Data APIs meeting, as finding a solution has been a bit frustrating. Basically there was three approaches I envisaged:

  1. Hardcoding these awkward cases is a somewhat easy solution (there's not a lot of these cases but there's not a few either)... certainly not ideal for long term maintability.
  2. Finding ways to merge Hypothesis strategies is difficult, as you can't usually just merge kwargs for the underling elements strategy (i.e. from_dtype()), but have to mix nearly-good base strategies with filters as well.
  3. Just generating a single minimal reproducer for these cases (i.e. not use Hypothesis) sounds good, as we don't really need to go overboard with these tests in the first place, but actually automating that is hard enough anyway (and Hypothesis is infact a good abstraction for specifying "valid data generators").

I have a working solution using Hypothesis, but I actually think it's use of Hypothesis is a bit inside-baseball and so not ideal for the long-term maintainability of the suite. I was thinking now that I could half-compromise with hard coding the combinations e.g. x1_i is finite and x1_i is greater than 0 and hook that in to the otherwise generalised test case generation.

In any case, I'll just get a solution in soon, and probably avoid revisiting this niche problem for a long time heh.

@asmeurer
Copy link
Member

asmeurer commented Mar 3, 2022

Vendoring meaning copying the test suite into the library's own test suite. It's true no one has been doing that yet, but it is something that we've supported up until now.

@honno
Copy link
Member Author

honno commented Mar 14, 2022

I resolved #40! I've documented the relevant areas, although generally there's a few small spots I'd like to cover still.

Generally I'm happy for this to get merged as-is. Here's the fairly simple TODOs I can think of for now or future PRs:

  • Equivalent operator tests
  • Test for non floating point special cases these cases aren't interesting i.e. round and trunc
  • Test for functions outside the elementwise family

And to fully replace generate_stubs.py, I'll need to:

  • Update array module stubbing
  • Update signature tests

Also, I'm not quite sold it's worth supporting hypothetical situations where users might not be able to use git submodules, which might be the case if the test suite was to be vendored. But in any case, I'd really like to not do this until I write a pre-commit script, lest we get out-of-sync stubs like last time.

  • Commit actual signatures files to remove need of git submodules

@honno honno requested a review from asmeurer March 14, 2022 17:47
@honno
Copy link
Member Author

honno commented Mar 14, 2022

I might write a separate issue on vendoring, just to note in today's meeting we discussed this... I realise a git submodule which is already inside a folder is not certainly not ideal heh. The current options seem to be:

  1. Hard code the signatures file ala generate_stubs.py
  2. Allow the user to specify somewhere (variable? env?) where the signatures folder/package is, so someone vendoring can vendor the spec too and use that

The timeline where libraries actually vendor might be a while though, which means we could give this some thought (and write an issue).

Generally, it seems to be a good idea to

  • have the submodule on the top-level

Copy link
Member Author

@honno honno left a comment

Choose a reason for hiding this comment

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

Ok, here's the remaining areas I think I should document. Shouldn't be as time consuming as say my BoundFromDtype docstring.

array_api_tests/test_special_cases.py Outdated Show resolved Hide resolved
array_api_tests/test_special_cases.py Outdated Show resolved Hide resolved
array_api_tests/test_special_cases.py Outdated Show resolved Hide resolved
array_api_tests/test_special_cases.py Show resolved Hide resolved
array_api_tests/test_special_cases.py Show resolved Hide resolved
array_api_tests/test_special_cases.py Outdated Show resolved Hide resolved
array_api_tests/test_special_cases.py Outdated Show resolved Hide resolved
@honno honno marked this pull request as ready for review March 16, 2022 16:14
@honno honno force-pushed the rst-spec branch 2 times, most recently from 3a2900d to 02dfb64 Compare March 17, 2022 13:47
@honno
Copy link
Member Author

honno commented Mar 18, 2022

@asmeurer happy to merge on my end—not critical but would nice to get this in soon so we can get feedback from say Dask about these tests. See #104 (comment) for notes on further work and vendoring.

@honno honno merged commit 5f2551c into data-apis:master Mar 22, 2022
@honno honno deleted the rst-spec branch February 28, 2024 13:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants