@@ -305,6 +305,138 @@ def test_group_add_command_name(runner):
305305 assert result .exit_code == 0
306306
307307
308+ @pytest .mark .parametrize (
309+ ("invocation_order" , "declaration_order" , "expected_order" ),
310+ [
311+ # Non-eager options.
312+ ([], ["-a" ], ["-a" ]),
313+ (["-a" ], ["-a" ], ["-a" ]),
314+ ([], ["-a" , "-c" ], ["-a" , "-c" ]),
315+ (["-a" ], ["-a" , "-c" ], ["-a" , "-c" ]),
316+ (["-c" ], ["-a" , "-c" ], ["-c" , "-a" ]),
317+ ([], ["-c" , "-a" ], ["-c" , "-a" ]),
318+ (["-a" ], ["-c" , "-a" ], ["-a" , "-c" ]),
319+ (["-c" ], ["-c" , "-a" ], ["-c" , "-a" ]),
320+ (["-a" , "-c" ], ["-a" , "-c" ], ["-a" , "-c" ]),
321+ (["-c" , "-a" ], ["-a" , "-c" ], ["-c" , "-a" ]),
322+ # Eager options.
323+ ([], ["-b" ], ["-b" ]),
324+ (["-b" ], ["-b" ], ["-b" ]),
325+ ([], ["-b" , "-d" ], ["-b" , "-d" ]),
326+ (["-b" ], ["-b" , "-d" ], ["-b" , "-d" ]),
327+ (["-d" ], ["-b" , "-d" ], ["-d" , "-b" ]),
328+ ([], ["-d" , "-b" ], ["-d" , "-b" ]),
329+ (["-b" ], ["-d" , "-b" ], ["-b" , "-d" ]),
330+ (["-d" ], ["-d" , "-b" ], ["-d" , "-b" ]),
331+ (["-b" , "-d" ], ["-b" , "-d" ], ["-b" , "-d" ]),
332+ (["-d" , "-b" ], ["-b" , "-d" ], ["-d" , "-b" ]),
333+ # Mixed options.
334+ ([], ["-a" , "-b" , "-c" , "-d" ], ["-b" , "-d" , "-a" , "-c" ]),
335+ (["-a" ], ["-a" , "-b" , "-c" , "-d" ], ["-b" , "-d" , "-a" , "-c" ]),
336+ (["-b" ], ["-a" , "-b" , "-c" , "-d" ], ["-b" , "-d" , "-a" , "-c" ]),
337+ (["-c" ], ["-a" , "-b" , "-c" , "-d" ], ["-b" , "-d" , "-c" , "-a" ]),
338+ (["-d" ], ["-a" , "-b" , "-c" , "-d" ], ["-d" , "-b" , "-a" , "-c" ]),
339+ (["-a" , "-b" ], ["-a" , "-b" , "-c" , "-d" ], ["-b" , "-d" , "-a" , "-c" ]),
340+ (["-b" , "-a" ], ["-a" , "-b" , "-c" , "-d" ], ["-b" , "-d" , "-a" , "-c" ]),
341+ (["-d" , "-c" ], ["-a" , "-b" , "-c" , "-d" ], ["-d" , "-b" , "-c" , "-a" ]),
342+ (["-c" , "-d" ], ["-a" , "-b" , "-c" , "-d" ], ["-d" , "-b" , "-c" , "-a" ]),
343+ (["-a" , "-b" , "-c" , "-d" ], ["-a" , "-b" , "-c" , "-d" ], ["-b" , "-d" , "-a" , "-c" ]),
344+ (["-b" , "-d" , "-a" , "-c" ], ["-a" , "-b" , "-c" , "-d" ], ["-b" , "-d" , "-a" , "-c" ]),
345+ ([], ["-b" , "-d" , "-e" , "-a" , "-c" ], ["-b" , "-d" , "-e" , "-a" , "-c" ]),
346+ (["-a" , "-d" ], ["-b" , "-d" , "-e" , "-a" , "-c" ], ["-d" , "-b" , "-e" , "-a" , "-c" ]),
347+ (["-c" , "-d" ], ["-b" , "-d" , "-e" , "-a" , "-c" ], ["-d" , "-b" , "-e" , "-c" , "-a" ]),
348+ ],
349+ )
350+ def test_iter_params_for_processing (
351+ invocation_order , declaration_order , expected_order
352+ ):
353+ parameters = {
354+ "-a" : click .Option (["-a" ]),
355+ "-b" : click .Option (["-b" ], is_eager = True ),
356+ "-c" : click .Option (["-c" ]),
357+ "-d" : click .Option (["-d" ], is_eager = True ),
358+ "-e" : click .Option (["-e" ], is_eager = True ),
359+ }
360+
361+ invocation_params = [parameters [opt_id ] for opt_id in invocation_order ]
362+ declaration_params = [parameters [opt_id ] for opt_id in declaration_order ]
363+ expected_params = [parameters [opt_id ] for opt_id in expected_order ]
364+
365+ assert (
366+ click .core .iter_params_for_processing (invocation_params , declaration_params )
367+ == expected_params
368+ )
369+
370+
371+ def test_help_param_priority (runner ):
372+ """Cover the edge-case in which the eagerness of help option was not
373+ respected, because it was internally generated multiple times.
374+
375+ See: https://github.com/pallets/click/pull/2811
376+ """
377+
378+ def print_and_exit (ctx , param , value ):
379+ if value :
380+ click .echo (f"Value of { param .name } is: { value } " )
381+ ctx .exit ()
382+
383+ @click .command (context_settings = {"help_option_names" : ("--my-help" ,)})
384+ @click .option ("-a" , is_flag = True , expose_value = False , callback = print_and_exit )
385+ @click .option (
386+ "-b" , is_flag = True , expose_value = False , callback = print_and_exit , is_eager = True
387+ )
388+ def cli ():
389+ pass
390+
391+ # --my-help is properly called and stop execution.
392+ result = runner .invoke (cli , ["--my-help" ])
393+ assert "Value of a is: True" not in result .stdout
394+ assert "Value of b is: True" not in result .stdout
395+ assert "--my-help" in result .stdout
396+ assert result .exit_code == 0
397+
398+ # -a is properly called and stop execution.
399+ result = runner .invoke (cli , ["-a" ])
400+ assert "Value of a is: True" in result .stdout
401+ assert "Value of b is: True" not in result .stdout
402+ assert "--my-help" not in result .stdout
403+ assert result .exit_code == 0
404+
405+ # -a takes precedence over -b and stop execution.
406+ result = runner .invoke (cli , ["-a" , "-b" ])
407+ assert "Value of a is: True" not in result .stdout
408+ assert "Value of b is: True" in result .stdout
409+ assert "--my-help" not in result .stdout
410+ assert result .exit_code == 0
411+
412+ # --my-help is eager by default so takes precedence over -a and stop
413+ # execution, whatever the order.
414+ for args in [["-a" , "--my-help" ], ["--my-help" , "-a" ]]:
415+ result = runner .invoke (cli , args )
416+ assert "Value of a is: True" not in result .stdout
417+ assert "Value of b is: True" not in result .stdout
418+ assert "--my-help" in result .stdout
419+ assert result .exit_code == 0
420+
421+ # Both -b and --my-help are eager so they're called in the order they're
422+ # invoked by the user.
423+ result = runner .invoke (cli , ["-b" , "--my-help" ])
424+ assert "Value of a is: True" not in result .stdout
425+ assert "Value of b is: True" in result .stdout
426+ assert "--my-help" not in result .stdout
427+ assert result .exit_code == 0
428+
429+ # But there was a bug when --my-help is called before -b, because the
430+ # --my-help option created by click via help_option_names is internally
431+ # created twice and is not the same object, breaking the priority order
432+ # produced by iter_params_for_processing.
433+ result = runner .invoke (cli , ["--my-help" , "-b" ])
434+ assert "Value of a is: True" not in result .stdout
435+ assert "Value of b is: True" not in result .stdout
436+ assert "--my-help" in result .stdout
437+ assert result .exit_code == 0
438+
439+
308440def test_unprocessed_options (runner ):
309441 @click .command (context_settings = dict (ignore_unknown_options = True ))
310442 @click .argument ("args" , nargs = - 1 , type = click .UNPROCESSED )
0 commit comments