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
test-driver: Run ptpython REPL at global scope #111486
base: master
Are you sure you want to change the base?
Conversation
This is a workaround for prompt-toolkit/ptpython#279, which was making it impossible to run the installer test interactively: >>> start_all() … >>> test_script() … machine # [ 382.137683] reboot: Power down (7.06 seconds) (8.23 seconds) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/nix/store/lbn2xnibdcvpgkpbrkdh2vlf1ap6930v-nixos-test-driver/bin/.nixos-test-driver-wrapped", line 888, in test_script exec(os.environ["testScript"]) File "<string>", line 69, in <module> File "<string>", line 18, in create_machine_named NameError: name 'default_flags' is not defined name 'default_flags' is not defined There’s no reason to expose the local variables of the run_tests function, anyway. Signed-off-by: Anders Kaseorg <andersk@mit.edu>
23bf65b
to
37ebe76
Compare
@@ -900,7 +900,7 @@ def run_tests() -> None: | |||
traceback.print_exc() | |||
sys.exit(1) | |||
else: | |||
ptpython.repl.embed(locals(), globals()) | |||
ptpython.repl.embed(globals()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ptpython.repl.embed(globals()) | |
ptpython.repl.embed(globals=globals()) |
if I read this correctly, we effectively previously ran this with globals=locals(), locals=globals()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You do. But we have no reason to pass locals()
anyway, and we don’t pass locals()
in the normal non-interactive mode, nor do we specify globals=
as a keyword argument to exec()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just meant explicitly setting globals
by kwarg makes this a bit less error-prone.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand, but when passing one argument, there’s no error to be prone to; the same argument is used as both globals
and locals
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
huh, that's not really obvious. Then maybe a comment explaining what we do and why?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you expand a bit more of the diff context, you’ll see we already do exec(tests, globals())
6 lines above this; exec
has clearer documentation but works exactly the same way. I don’t see any other way a reader might think it would work? What comment do you suggest?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just think we already mistakenly confused locals and globals (by relying on the number of arguments), and being a bit more descriptive in what we actually pass here as what wouldn't hurt. How things are handled if only one argument is passed isn't described anywhere in the function docstring either. Just using a subtle globals=
makes it clear.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remember, the main bug wasn’t that the arguments were provided in the wrong order—it’s that two arguments were provided at all. The bug would still have been there, just with different variables, if the two arguments had been provided in the right order:
>>> import ptpython
>>> a = 12
>>> def f(b):
... ptpython.repl.embed(globals(), locals())
...
>>> f(34)
>>> a
12
>>> (lambda: a)()
12
>>> b
34
>>> (lambda: b)()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 1, in <lambda>
NameError: name 'b' is not defined
name 'b' is not defined
It might have been less visible since we don’t actually need any of the locals from inside the function that called embed
. But that’s just another way of saying that the correct solution is to avoid providing the caller’s locals()
at all.
Now, if, in some hypothetical alternate reality, the locals
and globals
parameters of embed
were swapped in ptpython
, then in order for it to continue supporting one-argument calls, the swap would necessarily need to preserve the property that the second parameter defaults to the first parameter, not the property that the locals
parameter defaults to the globals
parameter.
-def embed(globals=None, locals=None, …):
+def embed(locals=None, globals=None, …):
...
- locals = locals or globals
+ globals = globals or locals
…
So embed(globals())
would still do the right thing, while embed(globals=globals())
would not. Therefore, even if we’re trying to be robust against counterfactual multiverse translocation, we really do want to provide the first argument here, not the globals
argument.
This may explain why exec(code, globals())
is idiomatic (see 6 lines above, and 2to3
’s translation of exec code in globals()
) and exec(code, globals=globals())
is not.
I understand the desire to find a subtle way to prevent this bug from happening in the future, but the best way to do that is to provide a correct call that can be referenced rather than an incorrect one. I’ll add a comment linking back here if it will help get this PR merged.
Requested in review. Signed-off-by: Anders Kaseorg <andersk@mit.edu>
I marked this as stale due to inactivity. → More info |
Motivation for this change
This is a workaround for prompt-toolkit/ptpython#279, which was making it impossible to run the installer test interactively:
There’s no reason to expose the local variables of the
run_tests
function, anyway.Things done
sandbox
innix.conf
on non-NixOS linux)nix-shell -p nixpkgs-review --run "nixpkgs-review wip"
./result/bin/
)nix path-info -S
before and after)