Two problems. The first one is that it should be check-equal?, not check-equal.
But fixing that will still result in the unbound id error. The documentation of require states:
The lexical context of the module-path form determines the context of the introduced identifiers
The issue in your code is that inside a macro (e.g. require-rackunit), the lexical context is that of the macro, which is not the right lexical context to be used.
This changes the lexical context of rackunit to the macro caller (stx), so your code will now work.
The code will fail again if you have another macro that calls require-rackunit, since the lexical context will be that of the outer macro. Arguably, that is the desirable result. But if you want that to work too, you need to keep threading the lexical context down, similar to what we are doing here.
One downside of syntax-local-introduce is that if require-rackunit is exported from a module, and then imported to be used in another module, it won’t work properly.
It’s unfortunate that the example of syntax-local-introduce in the documentation kinda implies that this exact problem can be solved by using syntax-local-introduce, while there’s a caveat like this.
This works when I use with-checks in the same module, but not when I import with-checks in another module. I'm trying to convert it to use datum->syntax, and I understand that I need to bind the lexical scope of the "test" module to the "(require rackunit)", but not sure how to do it.
[EDIT: Indeed this is N/A; more posts arrived while I was writing it and hit send. I guess I'll keep it, on the tiny chance it's useful to someone else someday.]
@sorawee gave a great answer to your specific question, @axmx, about your minimal example, where you define a macro to do a require at each site where the macro is used.
But I've rarely seen that done. If you're sure that's what want to do, and you're already familiar with Racket -- great; ignore the rest of this.
More typically, you'd define some module, which provides your own definitions and/or requires and re-provides definitions from other modules.
Keep in mind that Pyret’s function definition with a where clause does not faithfully translate into the test submodule. For one, a function definition with a where clause can appear at non-module context (e.g., nested within another function definition). But the expansion to the test submodule mandates that you must be at the module context. (You can probably use something like syntax-local-lift-module to workaround that though.)
Wait, really? I wasn’t aware of that drawback. Does the datum->syntax manipulation not have it? I suppose not, since you manipulate the contexts explicitly…
My understanding is that datum->syntax-ish or other related operations is the way to go precisely because of the issue with modules. syntax-local-introduce flips the fresh macro scope and the use-site scope but doesn't take care of module scopes.