-
Notifications
You must be signed in to change notification settings - Fork 707
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
Add an option to generate layout tests in a different file. #1655
Comments
Not really, the key is that those offsets come from clang itself, not from bindgen, so we know them to be right. Bindgen does a bunch of transformations, like adding padding when needed, adding custom types for bitfields, handle base classes, vtables, etc. For trivial structs like your example obviously it's pretty hard for bindgen to get wrong, but for edge cases it does catch bugs and has caught bugs in the past. See #1007 #1516 etc... Having a clear indication that something is wrong is much better than hitting weird memory corruptions. |
Alright, so those are more complicated. My experience has been colored by just binding various kinds of "plain C" headers I guess. Would it be appropriate for "plain C" structs (not using bitfields or vtables) to cut down on all the tests being output? |
You can do so yourself with |
Doesn't that block all layout tests though? |
Yes, it does... You want the size and alignment tests but not the field offset tests? |
I would like a default where the things that really need tests (bitfields and vtables) get tests, but plain C structs don't get tests. In my particular case of Xlib, there was a 50% file size difference (14k lines down to 7k lines), which actually caused my compile time to double as well because the bottleneck on bindings is just reading the disk and parsing it all. |
Well, the issue is that issues are often found when those complex structs get used in "simple" structs, so this is not as easy as it may seem. Maybe a good trade off for your case is generating tests only if |
What if it just made two module files, one for the real output and then a separate file for the tests, then the user can put that 2nd file in the |
That'd be a big-ish change, specially for the CLI, that could no longer output to stdout, or would need to have different commands to support both the old and the new behavior. Not necessarily opposed, but not sure if the trade offs make it worth it. |
good point! ah, here's an idea easy to implement: a flag for |
I think we could reasonably do that with or without a flag... But caveats below. I think it would be bad recommendation to make people rely on the order we emit the bindings, if only because it depends on system headers for example... So I thought it'd be reasonable to add a "test_destination" option of sorts (a If that sounds good, it's not too hard. But we need to figure out what to do when C++ namespaces are enabled... Right now the tests rely on having the structure name available on their scope. With namespaces we'd need to absolutize them, or such... |
That sounds great! I only use the CLI version, but I assume that this could be a CLI flag too? |
Sure, cli could use it too, I guess. |
Retitling to reflect the discussion. |
Plain C structs do need tests though. @Lokathor Can you show a single C struct for which a generated Rust struct would never need tests ? |
I said "don't get". |
Sure, and I understand that as a claim that these tests are not needed, since you also claimed:
Hence why I asked if there is a single struct for which that (no tests needed) is actually the case. |
Uh, weird request, I think that should be obvious but okay. typedef struct SDL_Point
{
int x;
int y;
} SDL_Point; |
So that code needs tests, because rust bindgen will generate, amongst other things: #[repr(C)]
struct SDL_Point {
x: user_provided::c_int,
y: user_provided::c_int,
} where Also, even if |
It's not the job of every single struct to check and recheck that the c_types declarations are correct. You only need to check them once at the top of the module. They won't change mid-module. Also, the tests are generated in terms of hard values (eg: |
You can have a header containing typedef struct SDL_Point
{
int x;
int y;
} SDL_Point; and for which #[repr(C)]
struct SDL_Point {
x: user_provided::c_int,
y: user_provided::c_int,
} but when linking you can link against a different version of your library which has the following type as C layout: typedef struct SDL_Point
{
long x; long y;
// or:
int* xs;
// or....
} SDL_Point; The current tests diagnose this problem, which is quite common (use previously-saved generated bindings while linking a newer version of the library). Checking |
Since the tests don't call into C and use the C sizeof operator on the current library version's definition of the struct I don't see how they'd catch that particular problem. Either way, Emilio had already very kindly agreed to put this change into the plans so I don't care to continue the discussion. |
The tests should call into C (that's how they are able to use C's |
Please look at this definition and the test that goes with it: https://github.com/Lokathor/fermium/blob/master/src/SDL2-v2.0.10-x86_64-pc-windows-msvc.rs#L2780 The generated tests don't call into C code at all.
|
My bad, I thought that bindgen would generate the tests in the same way as
libstd which calls into C and therefore the tests fail if the system where
the wrapper is built has an ABI incompatible version of the library. That
looks like a potential bindgen bug.
|
Bindgen generates tests calling into libclang, so yeah, the tests depend on the target you generate the bindings for. |
@emilio is it possible for bindgen to generate the tests on the fly while testing ? That is, generate the bindings on one target, which can be committed, or extended to support many targets, and when testing, check out the headers, and generate the bindings for the target in which the tests are happening ? |
Isn't that equivalent to running bindgen at build-time? |
Not really. You run bindgen at "binding generation time" to generate bindings, e.g., for a couple of architectures, and then just commit those files to your repo. At build time, you just build the You could run bindgen at build-time only for architectures that your current bindings do not support, as a "best effort" kind of thing, but for common ones users wouldn't even need to build |
Sure, call it "running bindgen at build time if We could add a flag for bindgen to only generate tests, then you could do something like that without too much trouble in your build script? |
I think that would go hand in hand with this feature (e.g. the ability to generate tests in a different file). |
Right. I guess once we implement this, doing that would be something equivalent to setting non_test_output to |
I poked at this. thomcc@db0f7ad is a mostly working implementation I nerd-sniped myself into doing. Unsure how to test it except manually. Also not sure it actually solves my problem... Will PR if interested though. |
Replying to #1651 (comment) as a new issue
My initial comment was "these generated tests sure don't seem to actually be useful" and then I was asked for an explanation.
So, I guess it depends on who these tests are for.
let's look at an example
(We're ignoring the "derefing a null pointer" UB, which the other issue covers, we have to ask ourselves what these tests are intended to check.)
So the Rust types have defined widths and alignments, but the C types are implemented as aliases and they theoretically vary by platform (
c_int
isn't necessarily alwaysi32
, it just usually is).Now maybe it's a useful thing to have a test where we assert the sizes we expect for the C types based on the local C types when we generated the bindings. You might care to know that the bindings were generated on one machine where C was like "x" and now you're on a machine where C is like "y".
If we do want to check that the local C environment is the same as the one bindgen saw when it ran, we should do that via a single test once at the top of the module:
We should not have tests per field per struct spread all across the module basically asserting that 2+2 is 4 for thousands of lines.
On the other hand, it's entirely possible that you don't care that the target C environment doesn't match the C environment at the time of running bindgen, because you expected that, and all the structs are defined in terms of the ctype aliases so with the local aliases they've already been corrected to the proper sizes. In this scenario, the current test suite system of being per field is also still mostly useless, since if you have just a single
test_local_c_env_matches_bindgen_c_env
function that tells you about the changes too.Either way, we don't need to have size, alignment, and field offset tests per struct. I can't see what scenario that it's guarding against that having a single test of the local ctypes doesn't solve.
The text was updated successfully, but these errors were encountered: