mirror of
https://gerrit.googlesource.com/git-repo
synced 2026-05-07 11:29:27 +00:00
tests: Add tests for repo status output
Build out status subcommand unit coverage using a minimal fake repo checkout wired through XmlManifest. The new tests verify: - clean status output prints the expected project header - modified tracked files appear with the expected status marker - `-o` output includes the orphan section and orphan entries - branch names shown in status reflect a started non-default branch Change-Id: Ia7c22593d0bbdc4aed81faeb168b846f3e4016ab Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/558501 Reviewed-by: Mike Frysinger <vapier@google.com> Tested-by: Nasser Grainawi <nasser.grainawi@oss.qualcomm.com> Commit-Queue: Nasser Grainawi <nasser.grainawi@oss.qualcomm.com> Reviewed-by: Gavin Mak <gavinmak@google.com>
This commit is contained in:
committed by
gerrit-scoped@luci-project-accounts.iam.gserviceaccount.com
parent
03fb18109f
commit
134eeb024b
@@ -0,0 +1,220 @@
|
||||
# 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.
|
||||
|
||||
"""Unittests for the status subcmd."""
|
||||
|
||||
import contextlib
|
||||
import io
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
from typing import List, Tuple
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
import utils_for_test
|
||||
|
||||
import manifest_xml
|
||||
import subcmds
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def repo_client_checkout(
|
||||
tmp_path: Path,
|
||||
) -> Tuple[Path, manifest_xml.XmlManifest]:
|
||||
"""Create a basic repo client checkout for status tests."""
|
||||
# Create in a subdir to avoid noise (like the repo_trace file).
|
||||
topdir = tmp_path / "client_checkout"
|
||||
repodir = topdir / ".repo"
|
||||
manifest_dir = repodir / "manifests"
|
||||
manifest_file = repodir / manifest_xml.MANIFEST_FILE_NAME
|
||||
|
||||
repodir.mkdir(parents=True)
|
||||
manifest_dir.mkdir()
|
||||
|
||||
gitdir = repodir / "manifests.git"
|
||||
gitdir.mkdir()
|
||||
(gitdir / "config").write_text(
|
||||
"""[remote "origin"]
|
||||
url = https://localhost:0/manifest
|
||||
verbose = false
|
||||
"""
|
||||
)
|
||||
|
||||
_init_temp_git_tree(manifest_dir)
|
||||
|
||||
manifest_file.write_text(
|
||||
"""
|
||||
<manifest>
|
||||
<remote name="origin" fetch="http://localhost" />
|
||||
<default remote="origin" revision="refs/heads/main" />
|
||||
<project name="proj" path="src/proj" />
|
||||
</manifest>
|
||||
""",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
(repodir / "projects" / "src" / "proj.git").mkdir(parents=True)
|
||||
(repodir / "project-objects" / "proj.git").mkdir(parents=True)
|
||||
|
||||
worktree = topdir / "src" / "proj"
|
||||
worktree.parent.mkdir(parents=True, exist_ok=True)
|
||||
_init_temp_git_tree(worktree)
|
||||
|
||||
manifest = manifest_xml.XmlManifest(str(repodir), str(manifest_file))
|
||||
return topdir, manifest
|
||||
|
||||
|
||||
def _init_temp_git_tree(git_dir: Path) -> None:
|
||||
"""Create a new git checkout with an initial commit for testing."""
|
||||
utils_for_test.init_git_tree(git_dir)
|
||||
(git_dir / "README").write_text("init")
|
||||
subprocess.check_call(["git", "add", "README"], cwd=git_dir)
|
||||
subprocess.check_call(["git", "commit", "-q", "-m", "init"], cwd=git_dir)
|
||||
|
||||
|
||||
def _run_status(manifest: manifest_xml.XmlManifest, argv: List[str]) -> None:
|
||||
"""Run the status subcommand with parsed options against a test manifest."""
|
||||
cmd = subcmds.status.Status()
|
||||
cmd.manifest = manifest
|
||||
cmd.client = mock.MagicMock(globalConfig=manifest.globalConfig)
|
||||
|
||||
opts, args = cmd.OptionParser.parse_args(argv + ["--jobs=1"])
|
||||
cmd.CommonValidateOptions(opts, args)
|
||||
|
||||
cmd.Execute(opts, args)
|
||||
|
||||
|
||||
def _status_lines(output: str) -> List[str]:
|
||||
"""Normalize path separators and split command output into lines."""
|
||||
return output.replace(os.sep, "/").splitlines()
|
||||
|
||||
|
||||
def _assert_project_header(line: str, project_path: str, branch: str) -> None:
|
||||
"""Assert a status project header line for a project and branch."""
|
||||
expected = f"project {(project_path + '/ '):<40}branch {branch}"
|
||||
assert line == expected
|
||||
|
||||
|
||||
def _assert_orphan_block(lines: List[str], expected: List[str]) -> None:
|
||||
"""Assert orphan block header and entries, independent of entry ordering."""
|
||||
assert lines
|
||||
assert lines[0] == ("Objects not within a project (orphans)")
|
||||
orphan_lines = lines[1:]
|
||||
assert len(orphan_lines) == len(expected)
|
||||
assert sorted(orphan_lines) == sorted(expected)
|
||||
|
||||
|
||||
def test_orphans_basic(
|
||||
repo_client_checkout: Tuple[Path, manifest_xml.XmlManifest],
|
||||
) -> None:
|
||||
"""Verify -o output includes project header and orphan block."""
|
||||
topdir, manifest = repo_client_checkout
|
||||
project_path = next(iter(manifest.paths.keys()))
|
||||
|
||||
(topdir / "src" / "orphan_dir").mkdir(parents=True)
|
||||
(topdir / "orphan.txt").write_text("data")
|
||||
|
||||
with contextlib.redirect_stdout(io.StringIO()) as stdout:
|
||||
_run_status(manifest, ["-o"])
|
||||
|
||||
lines = _status_lines(stdout.getvalue())
|
||||
_assert_project_header(lines[0], project_path, "main")
|
||||
_assert_orphan_block(
|
||||
lines[1:],
|
||||
[
|
||||
" --\torphan.txt",
|
||||
" --\tsrc/orphan_dir/",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def test_empty_status_without_orphans(
|
||||
repo_client_checkout: Tuple[Path, manifest_xml.XmlManifest],
|
||||
) -> None:
|
||||
"""Verify clean status without -o prints only the project header line."""
|
||||
_, manifest = repo_client_checkout
|
||||
project_path = next(iter(manifest.paths.keys()))
|
||||
|
||||
with contextlib.redirect_stdout(io.StringIO()) as stdout:
|
||||
_run_status(manifest, [])
|
||||
|
||||
lines = _status_lines(stdout.getvalue())
|
||||
assert len(lines) == 1
|
||||
_assert_project_header(lines[0], project_path, "main")
|
||||
|
||||
|
||||
def test_status_without_orphans(
|
||||
repo_client_checkout: Tuple[Path, manifest_xml.XmlManifest],
|
||||
) -> None:
|
||||
"""Verify modified tracked file appears in status output without -o."""
|
||||
topdir, manifest = repo_client_checkout
|
||||
project_path = next(iter(manifest.paths.keys()))
|
||||
|
||||
(topdir / project_path / "README").write_text("updated")
|
||||
|
||||
with contextlib.redirect_stdout(io.StringIO()) as stdout:
|
||||
_run_status(manifest, [])
|
||||
|
||||
lines = _status_lines(stdout.getvalue())
|
||||
assert len(lines) == 2
|
||||
_assert_project_header(lines[0], project_path, "main")
|
||||
assert lines[1] == " -m\tREADME"
|
||||
|
||||
|
||||
def test_status_with_orphans_and_modified_file(
|
||||
repo_client_checkout: Tuple[Path, manifest_xml.XmlManifest],
|
||||
) -> None:
|
||||
"""Verify modified-file status plus orphan block."""
|
||||
topdir, manifest = repo_client_checkout
|
||||
project_path = next(iter(manifest.paths.keys()))
|
||||
|
||||
(topdir / project_path / "README").write_text("updated")
|
||||
(topdir / "src" / "orphan_dir").mkdir(parents=True)
|
||||
(topdir / "orphan.txt").write_text("data")
|
||||
|
||||
with contextlib.redirect_stdout(io.StringIO()) as stdout:
|
||||
_run_status(manifest, ["-o"])
|
||||
|
||||
lines = _status_lines(stdout.getvalue())
|
||||
_assert_project_header(lines[0], project_path, "main")
|
||||
assert lines[1] == " -m\tREADME"
|
||||
_assert_orphan_block(
|
||||
lines[2:],
|
||||
[
|
||||
" --\torphan.txt",
|
||||
" --\tsrc/orphan_dir/",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def test_empty_status_after_start_shows_started_branch(
|
||||
repo_client_checkout: Tuple[Path, manifest_xml.XmlManifest],
|
||||
) -> None:
|
||||
"""Verify status shows the started branch name when the tree is clean."""
|
||||
topdir, manifest = repo_client_checkout
|
||||
|
||||
project_path = next(iter(manifest.paths.keys()))
|
||||
project_worktree = topdir / project_path
|
||||
started_branch = "topic/test-status-branch"
|
||||
subprocess.check_call(
|
||||
["git", "checkout", "-q", "-b", started_branch], cwd=project_worktree
|
||||
)
|
||||
|
||||
with contextlib.redirect_stdout(io.StringIO()) as stdout:
|
||||
_run_status(manifest, [])
|
||||
|
||||
lines = _status_lines(stdout.getvalue())
|
||||
assert len(lines) == 1
|
||||
_assert_project_header(lines[0], project_path, started_branch)
|
||||
Reference in New Issue
Block a user