mirror of
https://gerrit.googlesource.com/git-repo
synced 2026-05-08 03:49:28 +00:00
242e97d9dd
Similar to `git`, when a user types an unknown command like `repo tart`, we now use `difflib.get_close_matches` to suggest similar commands. If `help.autocorrect` is set in the git config, it will optionally prompt the user to automatically run the assumed command, or wait for a configured delay before executing it. Verification Steps: 1. Created a dummy repo project locally. 2. Verified `help.autocorrect=0|false|off|no|show` suggests command and exits. 3. Verified `help.autocorrect=1|true|on|yes|immediate` automatically runs suggestion. 4. Verified `help.autocorrect=<number>` runs after `<number>*0.1` seconds. 5. Verified `help.autocorrect=never` exits immediately without suggestions. 6. Verified `help.autocorrect=prompt` asks user to accept [y/n] and handles correctly. BUG: b/489753302 Change-Id: I6dcd63229cbd7badf5404459b48690c68f5b4857 Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/558021 Tested-by: Sam Saccone <samccone@google.com> Commit-Queue: Sam Saccone <samccone@google.com> Reviewed-by: Mike Frysinger <vapier@google.com>
167 lines
5.4 KiB
Python
167 lines
5.4 KiB
Python
# Copyright (C) 2026 The Android Open Source Project
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
"""Tests for the main repo script and subcommand routing."""
|
|
|
|
from unittest import mock
|
|
|
|
import pytest
|
|
|
|
from main import _Repo
|
|
|
|
|
|
@pytest.fixture(name="repo")
|
|
def fixture_repo():
|
|
repo = _Repo("repodir")
|
|
# Overriding the command list here ensures that we are only testing
|
|
# against a fixed set of commands, reducing fragility to new
|
|
# subcommands being added to the main repo tool.
|
|
repo.commands = {"start": None, "sync": None, "smart": None}
|
|
return repo
|
|
|
|
|
|
@pytest.fixture(name="mock_config")
|
|
def fixture_mock_config():
|
|
return mock.MagicMock()
|
|
|
|
|
|
@mock.patch("time.sleep")
|
|
def test_autocorrect_delay(mock_sleep, repo, mock_config):
|
|
"""Test autocorrect with positive delay."""
|
|
mock_config.GetString.return_value = "10"
|
|
|
|
res = repo._autocorrect_command_name("tart", mock_config)
|
|
|
|
mock_config.GetString.assert_called_with("help.autocorrect")
|
|
mock_sleep.assert_called_with(1.0)
|
|
assert res == "start"
|
|
|
|
|
|
@mock.patch("time.sleep")
|
|
def test_autocorrect_delay_one(mock_sleep, repo, mock_config):
|
|
"""Test autocorrect with '1' (0.1s delay, not immediate)."""
|
|
mock_config.GetString.return_value = "1"
|
|
|
|
res = repo._autocorrect_command_name("tart", mock_config)
|
|
|
|
mock_sleep.assert_called_with(0.1)
|
|
assert res == "start"
|
|
|
|
|
|
@mock.patch("time.sleep", side_effect=KeyboardInterrupt())
|
|
def test_autocorrect_delay_interrupt(mock_sleep, repo, mock_config):
|
|
"""Test autocorrect handles KeyboardInterrupt during delay."""
|
|
mock_config.GetString.return_value = "10"
|
|
|
|
res = repo._autocorrect_command_name("tart", mock_config)
|
|
|
|
mock_sleep.assert_called_with(1.0)
|
|
assert res is None
|
|
|
|
|
|
@mock.patch("time.sleep")
|
|
def test_autocorrect_immediate(mock_sleep, repo, mock_config):
|
|
"""Test autocorrect with immediate/negative delay."""
|
|
# Test numeric negative.
|
|
mock_config.GetString.return_value = "-1"
|
|
res = repo._autocorrect_command_name("tart", mock_config)
|
|
mock_sleep.assert_not_called()
|
|
assert res == "start"
|
|
|
|
# Test string boolean "true".
|
|
mock_config.GetString.return_value = "true"
|
|
res = repo._autocorrect_command_name("tart", mock_config)
|
|
mock_sleep.assert_not_called()
|
|
assert res == "start"
|
|
|
|
# Test string boolean "yes".
|
|
mock_config.GetString.return_value = "YES"
|
|
res = repo._autocorrect_command_name("tart", mock_config)
|
|
mock_sleep.assert_not_called()
|
|
assert res == "start"
|
|
|
|
# Test string boolean "immediate".
|
|
mock_config.GetString.return_value = "Immediate"
|
|
res = repo._autocorrect_command_name("tart", mock_config)
|
|
mock_sleep.assert_not_called()
|
|
assert res == "start"
|
|
|
|
|
|
def test_autocorrect_zero_or_show(repo, mock_config):
|
|
"""Test autocorrect with zero delay (suggestions only)."""
|
|
# Test numeric zero.
|
|
mock_config.GetString.return_value = "0"
|
|
res = repo._autocorrect_command_name("tart", mock_config)
|
|
assert res is None
|
|
|
|
# Test string boolean "false".
|
|
mock_config.GetString.return_value = "False"
|
|
res = repo._autocorrect_command_name("tart", mock_config)
|
|
assert res is None
|
|
|
|
# Test string boolean "show".
|
|
mock_config.GetString.return_value = "show"
|
|
res = repo._autocorrect_command_name("tart", mock_config)
|
|
assert res is None
|
|
|
|
|
|
def test_autocorrect_never(repo, mock_config):
|
|
"""Test autocorrect with 'never'."""
|
|
mock_config.GetString.return_value = "never"
|
|
res = repo._autocorrect_command_name("tart", mock_config)
|
|
assert res is None
|
|
|
|
|
|
@mock.patch("builtins.input", return_value="y")
|
|
def test_autocorrect_prompt_yes(mock_input, repo, mock_config):
|
|
"""Test autocorrect with prompt and user answers yes."""
|
|
mock_config.GetString.return_value = "prompt"
|
|
|
|
res = repo._autocorrect_command_name("tart", mock_config)
|
|
|
|
assert res == "start"
|
|
|
|
|
|
@mock.patch("builtins.input", return_value="n")
|
|
def test_autocorrect_prompt_no(mock_input, repo, mock_config):
|
|
"""Test autocorrect with prompt and user answers no."""
|
|
mock_config.GetString.return_value = "prompt"
|
|
|
|
res = repo._autocorrect_command_name("tart", mock_config)
|
|
|
|
assert res is None
|
|
|
|
|
|
@mock.patch("builtins.input", return_value="y")
|
|
def test_autocorrect_multiple_candidates(mock_input, repo, mock_config):
|
|
"""Test autocorrect with multiple matches forces a prompt."""
|
|
mock_config.GetString.return_value = "10" # Normally just delay
|
|
|
|
# 'snart' matches both 'start' and 'smart' with > 0.7 ratio
|
|
res = repo._autocorrect_command_name("snart", mock_config)
|
|
|
|
# Because there are multiple candidates, it should prompt
|
|
mock_input.assert_called_once()
|
|
assert res == "start"
|
|
|
|
|
|
@mock.patch("builtins.input", side_effect=KeyboardInterrupt())
|
|
def test_autocorrect_prompt_interrupt(mock_input, repo, mock_config):
|
|
"""Test autocorrect with prompt and user interrupts."""
|
|
mock_config.GetString.return_value = "prompt"
|
|
|
|
res = repo._autocorrect_command_name("tart", mock_config)
|
|
|
|
assert res is None
|