Compose fixes to a fixpoint and learn only from success
New thefuck/compose.py resolve() iterates the auto-run sources (learned-db exact, error-suggestion, history, path guess, help) per round on the previous round's script until stable, a revisit aborts or no source has a safe candidate; dangerous candidates are declined mid-round and consultation continues. Output-aware sources get a synthesized Command wrapping the round's script with the original failure's output. Zero accepted steps return None so the original failing script is never auto-executed; no round cap (termination contract: visited-set + fixed-range/word-count-preserving sources, stated in the module docstring; a compose-local memo acts only as a determinism tripwire). fix_command runs the inline chain through the composer; under settings.repeat or a non-nushell shell it records immediately before the run unless the only step replayed a learned-db exact hit; under nushell it closes the learned db first and records on returncode 0, forgets on failure (poison self-heal; CorrectedCommand.run now stashes the nu returncode on the instance). learned.forget(original, corrected) is the exact inverse of record (cmd: + matching-count word:/part: deletes, assembly failures are a documented no-op); learned.close_db is idempotent and atexit now registers the stable close_db method exactly once across close/reopen cycles. Test migration (authorized): test_fix_command_learned.py rebased to composer semantics (per-round consultation incl. error-suggestion; input-aware fakes so round 2 declines, preserving the recordings==[] pin in test_exact_learned_wins_over_guess).
This commit is contained in:
@@ -2,61 +2,92 @@ import pytest
|
||||
from mock import Mock, patch
|
||||
from thefuck import danger as real_danger
|
||||
from thefuck.entrypoints.fix_command import fix_command
|
||||
from thefuck.types import CorrectedCommand
|
||||
from thefuck.shells import Nushell
|
||||
from thefuck.types import Command, CorrectedCommand
|
||||
|
||||
ORIGINAL = "git psuh origin main"
|
||||
CORRECTED = "git push origin main"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_learned(monkeypatch):
|
||||
state = {"correction": None, "guess": None, "history": None,
|
||||
"help": None, "recordings": [], "calls": []}
|
||||
state = {"correction": None, "suggestion": None, "history": None,
|
||||
"guess": None, "help": None, "recordings": [], "calls": [],
|
||||
"forgets": [], "order": [], "closes": 0}
|
||||
# The real gate fail-safes to True without bashlex, which would
|
||||
# make every auto-apply test platform-dependent; the danger
|
||||
# override tests re-install the real module.
|
||||
# override tests re-install the real module in both consumers.
|
||||
fake_danger = Mock()
|
||||
fake_danger.is_dangerous.return_value = False
|
||||
monkeypatch.setattr(
|
||||
"thefuck.entrypoints.fix_command.danger", fake_danger
|
||||
)
|
||||
monkeypatch.setattr("thefuck.compose.danger", fake_danger)
|
||||
|
||||
def fake_get_correction(script):
|
||||
state["calls"].append("correction")
|
||||
return state["correction"]
|
||||
def scripted(key, script):
|
||||
value = state[key]
|
||||
if callable(value):
|
||||
return value(script)
|
||||
# Input-aware: a constant candidate is scripted for the
|
||||
# original script only, so round 2 of the composition
|
||||
# declines instead of accepting it again.
|
||||
return value if script == ORIGINAL else None
|
||||
|
||||
def fake_history(command):
|
||||
state["calls"].append("history")
|
||||
return state["history"]
|
||||
def script_source(key, label):
|
||||
def fake(script):
|
||||
state["calls"].append(label)
|
||||
return scripted(key, script)
|
||||
return fake
|
||||
|
||||
def fake_guess(script):
|
||||
state["calls"].append("guess")
|
||||
return state["guess"]
|
||||
|
||||
def fake_help(script):
|
||||
state["calls"].append("help")
|
||||
return state["help"]
|
||||
def command_source(key, label):
|
||||
def fake(command):
|
||||
state["calls"].append(label)
|
||||
return scripted(key, command.script)
|
||||
return fake
|
||||
|
||||
def fake_record(original, corrected):
|
||||
state["order"].append("record")
|
||||
state["recordings"].append((original, corrected))
|
||||
|
||||
def fake_forget(original, corrected):
|
||||
state["order"].append("forget")
|
||||
state["forgets"].append((original, corrected))
|
||||
|
||||
def fake_close_db():
|
||||
state["order"].append("close")
|
||||
state["closes"] += 1
|
||||
|
||||
monkeypatch.setattr(
|
||||
"thefuck.entrypoints.fix_command.get_correction", fake_get_correction
|
||||
"thefuck.compose.get_correction",
|
||||
script_source("correction", "learned")
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"thefuck.entrypoints.fix_command.get_history_correction", fake_history
|
||||
"thefuck.compose.get_suggestion_candidates",
|
||||
command_source("suggestion", "error-suggestion")
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"thefuck.entrypoints.fix_command.guess_from_path", fake_guess
|
||||
"thefuck.compose.get_history_correction",
|
||||
command_source("history", "history")
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"thefuck.entrypoints.fix_command.get_help_correction", fake_help
|
||||
"thefuck.compose.guess_from_path", script_source("guess", "path")
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"thefuck.compose.get_help_correction", script_source("help", "help")
|
||||
)
|
||||
fake_learned = Mock(record=fake_record, forget=fake_forget,
|
||||
close_db=fake_close_db)
|
||||
monkeypatch.setattr(
|
||||
"thefuck.entrypoints.fix_command.learned", fake_learned
|
||||
)
|
||||
monkeypatch.setattr("thefuck.entrypoints.fix_command.record", fake_record)
|
||||
return state
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def known_args():
|
||||
return Mock(
|
||||
force_command="git psuh origin main", yes=False, debug=False, repeat=False
|
||||
force_command="git psuh origin main", yes=False, debug=False,
|
||||
repeat=False
|
||||
)
|
||||
|
||||
|
||||
@@ -64,9 +95,10 @@ class TestLearnedAutoApply(object):
|
||||
def test_auto_applies_learned_correction(
|
||||
self, mock_learned, known_args, settings, monkeypatch
|
||||
):
|
||||
mock_learned["correction"] = "git push origin main"
|
||||
mock_learned["correction"] = CORRECTED
|
||||
monkeypatch.setattr(
|
||||
"thefuck.entrypoints.fix_command.get_corrected_commands", lambda _: iter([])
|
||||
"thefuck.entrypoints.fix_command.get_corrected_commands",
|
||||
lambda _: iter([])
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"thefuck.entrypoints.fix_command.select_command", lambda _: None
|
||||
@@ -81,10 +113,11 @@ class TestLearnedAutoApply(object):
|
||||
def test_learned_skips_rule_matching(
|
||||
self, mock_learned, known_args, settings, monkeypatch
|
||||
):
|
||||
mock_learned["correction"] = "git push origin main"
|
||||
mock_learned["correction"] = CORRECTED
|
||||
get_corrected = Mock()
|
||||
monkeypatch.setattr(
|
||||
"thefuck.entrypoints.fix_command.get_corrected_commands", get_corrected
|
||||
"thefuck.entrypoints.fix_command.get_corrected_commands",
|
||||
get_corrected
|
||||
)
|
||||
|
||||
with patch("thefuck.types.CorrectedCommand.run"), patch(
|
||||
@@ -96,7 +129,7 @@ class TestLearnedAutoApply(object):
|
||||
def test_shows_corrected_command_on_auto_apply(
|
||||
self, mock_learned, known_args, settings, monkeypatch
|
||||
):
|
||||
mock_learned["correction"] = "git push origin main"
|
||||
mock_learned["correction"] = CORRECTED
|
||||
|
||||
with patch("thefuck.types.CorrectedCommand.run"), patch(
|
||||
"thefuck.logs.show_corrected_command"
|
||||
@@ -104,17 +137,18 @@ class TestLearnedAutoApply(object):
|
||||
fix_command(known_args)
|
||||
assert mock_show.call_count == 1
|
||||
shown_cmd = mock_show.call_args[0][0]
|
||||
assert shown_cmd.script == "git push origin main"
|
||||
assert shown_cmd.script == CORRECTED
|
||||
|
||||
|
||||
class TestGuessAutoApply(object):
|
||||
def test_guess_records_and_auto_applies(
|
||||
self, mock_learned, known_args, settings, monkeypatch
|
||||
):
|
||||
mock_learned["guess"] = "git push origin main"
|
||||
mock_learned["guess"] = CORRECTED
|
||||
get_corrected = Mock()
|
||||
monkeypatch.setattr(
|
||||
"thefuck.entrypoints.fix_command.get_corrected_commands", get_corrected
|
||||
"thefuck.entrypoints.fix_command.get_corrected_commands",
|
||||
get_corrected
|
||||
)
|
||||
|
||||
with patch("thefuck.types.CorrectedCommand.run") as mock_run, patch(
|
||||
@@ -123,15 +157,13 @@ class TestGuessAutoApply(object):
|
||||
fix_command(known_args)
|
||||
mock_run.assert_called_once()
|
||||
get_corrected.assert_not_called()
|
||||
assert mock_learned["recordings"] == [
|
||||
("git psuh origin main", "git push origin main")
|
||||
]
|
||||
assert mock_learned["recordings"] == [(ORIGINAL, CORRECTED)]
|
||||
|
||||
def test_exact_learned_wins_over_guess(
|
||||
self, mock_learned, known_args, settings, monkeypatch
|
||||
):
|
||||
mock_learned["correction"] = "git push origin dev"
|
||||
mock_learned["guess"] = "git push origin main"
|
||||
mock_learned["guess"] = CORRECTED
|
||||
|
||||
with patch("thefuck.types.CorrectedCommand.run"), patch(
|
||||
"thefuck.logs.show_corrected_command"
|
||||
@@ -143,13 +175,18 @@ class TestGuessAutoApply(object):
|
||||
|
||||
|
||||
class TestResolverChainOrder(object):
|
||||
# Round 1 consults [learned, error-suggestion, history, path,
|
||||
# help] up to the round's winner; round 2 consults all five with
|
||||
# the corrected script, the input-aware fakes decline and the
|
||||
# composition ends at a stable point.
|
||||
def test_full_chain_consulted_in_order_when_only_help_hits(
|
||||
self, mock_learned, known_args, settings, monkeypatch
|
||||
):
|
||||
mock_learned["help"] = "git push origin main"
|
||||
mock_learned["help"] = CORRECTED
|
||||
get_corrected = Mock()
|
||||
monkeypatch.setattr(
|
||||
"thefuck.entrypoints.fix_command.get_corrected_commands", get_corrected
|
||||
"thefuck.entrypoints.fix_command.get_corrected_commands",
|
||||
get_corrected
|
||||
)
|
||||
|
||||
with patch("thefuck.types.CorrectedCommand.run"), patch(
|
||||
@@ -158,18 +195,24 @@ class TestResolverChainOrder(object):
|
||||
fix_command(known_args)
|
||||
|
||||
assert mock_learned["calls"] == [
|
||||
"correction", "history", "guess", "help"
|
||||
"learned", "error-suggestion", "history", "path", "help",
|
||||
"learned", "error-suggestion", "history", "path", "help",
|
||||
]
|
||||
get_corrected.assert_not_called()
|
||||
|
||||
@pytest.mark.parametrize("hit_source,expected_calls", [
|
||||
("history", ["correction", "history"]),
|
||||
("guess", ["correction", "history", "guess"]),
|
||||
("history", ["learned", "error-suggestion", "history",
|
||||
"learned", "error-suggestion", "history",
|
||||
"path", "help"]),
|
||||
("guess", ["learned", "error-suggestion", "history", "path",
|
||||
"learned", "error-suggestion", "history", "path",
|
||||
"help"]),
|
||||
])
|
||||
def test_earlier_hit_stops_the_chain(
|
||||
self, mock_learned, known_args, settings, hit_source, expected_calls
|
||||
def test_earlier_hit_stops_the_round(
|
||||
self, mock_learned, known_args, settings, monkeypatch, hit_source,
|
||||
expected_calls
|
||||
):
|
||||
mock_learned[hit_source] = "git push origin main"
|
||||
mock_learned[hit_source] = CORRECTED
|
||||
|
||||
with patch("thefuck.types.CorrectedCommand.run"), patch(
|
||||
"thefuck.logs.show_corrected_command"
|
||||
@@ -183,7 +226,7 @@ class TestResolverChainOrder(object):
|
||||
):
|
||||
history_mock = Mock(return_value=None)
|
||||
monkeypatch.setattr(
|
||||
"thefuck.entrypoints.fix_command.get_history_correction", history_mock
|
||||
"thefuck.compose.get_history_correction", history_mock
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"thefuck.entrypoints.fix_command.get_corrected_commands",
|
||||
@@ -197,7 +240,7 @@ class TestResolverChainOrder(object):
|
||||
fix_command(known_args)
|
||||
|
||||
assert history_mock.call_count == 1
|
||||
assert history_mock.call_args[0][0].script == "git psuh origin main"
|
||||
assert history_mock.call_args[0][0].script == ORIGINAL
|
||||
|
||||
|
||||
class TestResolverAutoApply(object):
|
||||
@@ -205,10 +248,11 @@ class TestResolverAutoApply(object):
|
||||
def test_resolver_hit_records_and_auto_applies(
|
||||
self, mock_learned, known_args, settings, monkeypatch, source
|
||||
):
|
||||
mock_learned[source] = "git push origin main"
|
||||
mock_learned[source] = CORRECTED
|
||||
get_corrected = Mock()
|
||||
monkeypatch.setattr(
|
||||
"thefuck.entrypoints.fix_command.get_corrected_commands", get_corrected
|
||||
"thefuck.entrypoints.fix_command.get_corrected_commands",
|
||||
get_corrected
|
||||
)
|
||||
|
||||
with patch("thefuck.types.CorrectedCommand.run") as mock_run, patch(
|
||||
@@ -217,25 +261,44 @@ class TestResolverAutoApply(object):
|
||||
fix_command(known_args)
|
||||
mock_run.assert_called_once()
|
||||
get_corrected.assert_not_called()
|
||||
assert mock_show.call_args[0][0].script == "git push origin main"
|
||||
assert mock_learned["recordings"] == [
|
||||
("git psuh origin main", "git push origin main")
|
||||
]
|
||||
assert mock_show.call_args[0][0].script == CORRECTED
|
||||
assert mock_learned["recordings"] == [(ORIGINAL, CORRECTED)]
|
||||
|
||||
|
||||
class TestComposition(object):
|
||||
def test_two_sources_compose_into_one_run(
|
||||
self, mock_learned, known_args, settings, monkeypatch
|
||||
):
|
||||
mock_learned["guess"] = "git psuh origin main"
|
||||
mock_learned["history"] = (
|
||||
lambda script: CORRECTED if script == "git psuh origin main"
|
||||
else None)
|
||||
|
||||
with patch("thefuck.types.CorrectedCommand.run") as mock_run, patch(
|
||||
"thefuck.logs.show_corrected_command"
|
||||
) as mock_show:
|
||||
fix_command(known_args)
|
||||
mock_run.assert_called_once()
|
||||
assert mock_show.call_args[0][0].script == CORRECTED
|
||||
assert mock_learned["recordings"] == [(ORIGINAL, CORRECTED)]
|
||||
|
||||
|
||||
class TestDangerOverride(object):
|
||||
@pytest.mark.parametrize(
|
||||
"hit_source", ["correction", "history", "guess", "help"])
|
||||
"hit_source",
|
||||
["correction", "suggestion", "history", "guess", "help"])
|
||||
def test_dangerous_hit_asks_instead_of_auto_running(
|
||||
self, mock_learned, known_args, settings, monkeypatch, hit_source
|
||||
):
|
||||
# The real gate: a danger-flagged candidate from ANY source —
|
||||
# a seeded learned-db exact hit included — reaches
|
||||
# select_command and nothing auto-runs. The abort via the
|
||||
# mocked selection keeps the pin free of terminal IO.
|
||||
# a seeded learned-db exact hit included — is declined by the
|
||||
# composer, falls to select_command and nothing auto-runs. The
|
||||
# abort via the mocked selection keeps the pin free of
|
||||
# terminal IO.
|
||||
monkeypatch.setattr(
|
||||
"thefuck.entrypoints.fix_command.danger", real_danger
|
||||
)
|
||||
monkeypatch.setattr("thefuck.compose.danger", real_danger)
|
||||
mock_learned[hit_source] = "rm -rf /"
|
||||
select = Mock(return_value=None)
|
||||
monkeypatch.setattr(
|
||||
@@ -262,27 +325,27 @@ class TestRecordOnSelection(object):
|
||||
self, mock_learned, known_args, settings, monkeypatch
|
||||
):
|
||||
selected = CorrectedCommand(
|
||||
script="git push origin main", side_effect=None, priority=100
|
||||
script=CORRECTED, side_effect=None, priority=100
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"thefuck.entrypoints.fix_command.get_corrected_commands",
|
||||
lambda _: iter([selected]),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"thefuck.entrypoints.fix_command.select_command", lambda _: selected
|
||||
"thefuck.entrypoints.fix_command.select_command",
|
||||
lambda _: selected
|
||||
)
|
||||
|
||||
with patch("thefuck.types.CorrectedCommand.run"):
|
||||
fix_command(known_args)
|
||||
assert mock_learned["recordings"] == [
|
||||
("git psuh origin main", "git push origin main")
|
||||
]
|
||||
assert mock_learned["recordings"] == [(ORIGINAL, CORRECTED)]
|
||||
|
||||
def test_does_not_record_on_abort(
|
||||
self, mock_learned, known_args, settings, monkeypatch
|
||||
):
|
||||
monkeypatch.setattr(
|
||||
"thefuck.entrypoints.fix_command.get_corrected_commands", lambda _: iter([])
|
||||
"thefuck.entrypoints.fix_command.get_corrected_commands",
|
||||
lambda _: iter([])
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"thefuck.entrypoints.fix_command.select_command", lambda _: None
|
||||
@@ -298,14 +361,15 @@ class TestFallthrough(object):
|
||||
self, mock_learned, known_args, settings, monkeypatch
|
||||
):
|
||||
selected = CorrectedCommand(
|
||||
script="git push origin main", side_effect=None, priority=100
|
||||
script=CORRECTED, side_effect=None, priority=100
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"thefuck.entrypoints.fix_command.get_corrected_commands",
|
||||
lambda _: iter([selected]),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"thefuck.entrypoints.fix_command.select_command", lambda _: selected
|
||||
"thefuck.entrypoints.fix_command.select_command",
|
||||
lambda _: selected
|
||||
)
|
||||
|
||||
with patch("thefuck.types.CorrectedCommand.run") as mock_run:
|
||||
@@ -316,7 +380,7 @@ class TestFallthrough(object):
|
||||
self, mock_learned, known_args, settings, monkeypatch
|
||||
):
|
||||
selected = CorrectedCommand(
|
||||
script="git push origin main", side_effect=None, priority=100
|
||||
script=CORRECTED, side_effect=None, priority=100
|
||||
)
|
||||
select = Mock(return_value=selected)
|
||||
monkeypatch.setattr(
|
||||
@@ -332,6 +396,131 @@ class TestFallthrough(object):
|
||||
mock_run.assert_called_once()
|
||||
|
||||
assert mock_learned["calls"] == [
|
||||
"correction", "history", "guess", "help"
|
||||
"learned", "error-suggestion", "history", "path", "help"
|
||||
]
|
||||
assert select.call_count == 1
|
||||
|
||||
|
||||
class TestOutcomeLearning(object):
|
||||
@pytest.fixture
|
||||
def nushell(self, monkeypatch):
|
||||
monkeypatch.setattr(
|
||||
"thefuck.entrypoints.fix_command.shell", Nushell())
|
||||
|
||||
def _run_with_returncode(self, order, returncode):
|
||||
def run(cmd_self, old_cmd):
|
||||
order.append("run")
|
||||
cmd_self.returncode = returncode
|
||||
return run
|
||||
|
||||
def test_success_records_after_run(
|
||||
self, mock_learned, known_args, settings, monkeypatch, nushell
|
||||
):
|
||||
mock_learned["guess"] = CORRECTED
|
||||
|
||||
with patch("thefuck.types.CorrectedCommand.run",
|
||||
autospec=True) as mock_run, patch(
|
||||
"thefuck.logs.show_corrected_command"):
|
||||
mock_run.side_effect = self._run_with_returncode(
|
||||
mock_learned["order"], 0)
|
||||
fix_command(known_args)
|
||||
|
||||
assert mock_learned["order"] == ["close", "run", "record"]
|
||||
assert mock_learned["recordings"] == [(ORIGINAL, CORRECTED)]
|
||||
assert mock_learned["forgets"] == []
|
||||
|
||||
def test_failure_forgets_after_run(
|
||||
self, mock_learned, known_args, settings, monkeypatch, nushell
|
||||
):
|
||||
mock_learned["guess"] = CORRECTED
|
||||
|
||||
with patch("thefuck.types.CorrectedCommand.run",
|
||||
autospec=True) as mock_run, patch(
|
||||
"thefuck.logs.show_corrected_command"):
|
||||
mock_run.side_effect = self._run_with_returncode(
|
||||
mock_learned["order"], 1)
|
||||
fix_command(known_args)
|
||||
|
||||
assert mock_learned["order"] == ["close", "run", "forget"]
|
||||
assert mock_learned["forgets"] == [(ORIGINAL, CORRECTED)]
|
||||
assert mock_learned["recordings"] == []
|
||||
|
||||
def test_learned_replay_failure_still_forgets(
|
||||
self, mock_learned, known_args, settings, monkeypatch, nushell
|
||||
):
|
||||
# Poison self-heal: a seeded exact hit that fails on rerun is
|
||||
# removed, so the no-rerecord carve-out must not skip the
|
||||
# outcome gating.
|
||||
mock_learned["correction"] = CORRECTED
|
||||
|
||||
with patch("thefuck.types.CorrectedCommand.run",
|
||||
autospec=True) as mock_run, patch(
|
||||
"thefuck.logs.show_corrected_command"):
|
||||
mock_run.side_effect = self._run_with_returncode(
|
||||
mock_learned["order"], 1)
|
||||
fix_command(known_args)
|
||||
|
||||
assert mock_learned["order"] == ["close", "run", "forget"]
|
||||
assert mock_learned["forgets"] == [(ORIGINAL, CORRECTED)]
|
||||
|
||||
def test_repeat_records_before_run_without_outcome_gating(
|
||||
self, mock_learned, known_args, settings, monkeypatch, nushell
|
||||
):
|
||||
known_args.repeat = True
|
||||
mock_learned["guess"] = CORRECTED
|
||||
|
||||
with patch("thefuck.types.CorrectedCommand.run",
|
||||
autospec=True) as mock_run, patch(
|
||||
"thefuck.logs.show_corrected_command"):
|
||||
mock_run.side_effect = self._run_with_returncode(
|
||||
mock_learned["order"], 1)
|
||||
fix_command(known_args)
|
||||
|
||||
assert mock_learned["order"] == ["record", "run"]
|
||||
assert mock_learned["recordings"] == [(ORIGINAL, CORRECTED)]
|
||||
assert mock_learned["closes"] == 0
|
||||
|
||||
def test_other_shells_record_before_run(
|
||||
self, mock_learned, known_args, settings, monkeypatch
|
||||
):
|
||||
mock_learned["guess"] = CORRECTED
|
||||
|
||||
with patch("thefuck.types.CorrectedCommand.run",
|
||||
autospec=True) as mock_run, patch(
|
||||
"thefuck.logs.show_corrected_command"):
|
||||
mock_run.side_effect = self._run_with_returncode(
|
||||
mock_learned["order"], 1)
|
||||
fix_command(known_args)
|
||||
|
||||
assert mock_learned["order"] == ["record", "run"]
|
||||
assert mock_learned["closes"] == 0
|
||||
|
||||
def test_learned_only_win_does_not_rerecord_on_other_shells(
|
||||
self, mock_learned, known_args, settings, monkeypatch
|
||||
):
|
||||
mock_learned["correction"] = CORRECTED
|
||||
|
||||
with patch("thefuck.types.CorrectedCommand.run",
|
||||
autospec=True) as mock_run, patch(
|
||||
"thefuck.logs.show_corrected_command"):
|
||||
mock_run.side_effect = self._run_with_returncode(
|
||||
mock_learned["order"], 0)
|
||||
fix_command(known_args)
|
||||
|
||||
assert mock_learned["order"] == ["run"]
|
||||
assert mock_learned["recordings"] == []
|
||||
|
||||
|
||||
class TestNushellRunStashesReturncode(object):
|
||||
def test_run_stashes_nu_returncode_on_the_instance(
|
||||
self, settings, monkeypatch
|
||||
):
|
||||
settings.alter_history = False
|
||||
monkeypatch.setattr("thefuck.types.shell", Nushell())
|
||||
completed = Mock(returncode=3)
|
||||
with patch("thefuck.types.subprocess.run",
|
||||
return_value=completed) as mock_spawn:
|
||||
corrected = CorrectedCommand("git push", None, 0)
|
||||
corrected.run(Command("git psuh", ""))
|
||||
mock_spawn.assert_called_once_with(["nu", "-c", "git push"])
|
||||
assert corrected.returncode == 3
|
||||
|
||||
Reference in New Issue
Block a user