summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNasser Grainawi <nasser.grainawi@oss.qualcomm.com>2026-03-05 10:44:21 -0800
committergerrit-scoped@luci-project-accounts.iam.gserviceaccount.com <gerrit-scoped@luci-project-accounts.iam.gserviceaccount.com>2026-04-23 17:52:09 -0700
commit134eeb024bb172f425ef0823485b5cb537112f6c (patch)
tree037b748e817368aed38a1840a4df5620e63b86c0
parent03fb18109fb0db2522f36309242d16d0f83f17d2 (diff)
downloadgit-repo-134eeb024bb172f425ef0823485b5cb537112f6c.tar.gz
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>
-rw-r--r--tests/test_subcmds_status.py220
1 files changed, 220 insertions, 0 deletions
diff --git a/tests/test_subcmds_status.py b/tests/test_subcmds_status.py
new file mode 100644
index 000000000..6007878b2
--- /dev/null
+++ b/tests/test_subcmds_status.py
@@ -0,0 +1,220 @@
1# Copyright (C) 2026 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""Unittests for the status subcmd."""
16
17import contextlib
18import io
19import os
20from pathlib import Path
21import subprocess
22from typing import List, Tuple
23from unittest import mock
24
25import pytest
26import utils_for_test
27
28import manifest_xml
29import subcmds
30
31
32@pytest.fixture
33def repo_client_checkout(
34 tmp_path: Path,
35) -> Tuple[Path, manifest_xml.XmlManifest]:
36 """Create a basic repo client checkout for status tests."""
37 # Create in a subdir to avoid noise (like the repo_trace file).
38 topdir = tmp_path / "client_checkout"
39 repodir = topdir / ".repo"
40 manifest_dir = repodir / "manifests"
41 manifest_file = repodir / manifest_xml.MANIFEST_FILE_NAME
42
43 repodir.mkdir(parents=True)
44 manifest_dir.mkdir()
45
46 gitdir = repodir / "manifests.git"
47 gitdir.mkdir()
48 (gitdir / "config").write_text(
49 """[remote "origin"]
50 url = https://localhost:0/manifest
51 verbose = false
52 """
53 )
54
55 _init_temp_git_tree(manifest_dir)
56
57 manifest_file.write_text(
58 """
59 <manifest>
60 <remote name="origin" fetch="http://localhost" />
61 <default remote="origin" revision="refs/heads/main" />
62 <project name="proj" path="src/proj" />
63 </manifest>
64 """,
65 encoding="utf-8",
66 )
67
68 (repodir / "projects" / "src" / "proj.git").mkdir(parents=True)
69 (repodir / "project-objects" / "proj.git").mkdir(parents=True)
70
71 worktree = topdir / "src" / "proj"
72 worktree.parent.mkdir(parents=True, exist_ok=True)
73 _init_temp_git_tree(worktree)
74
75 manifest = manifest_xml.XmlManifest(str(repodir), str(manifest_file))
76 return topdir, manifest
77
78
79def _init_temp_git_tree(git_dir: Path) -> None:
80 """Create a new git checkout with an initial commit for testing."""
81 utils_for_test.init_git_tree(git_dir)
82 (git_dir / "README").write_text("init")
83 subprocess.check_call(["git", "add", "README"], cwd=git_dir)
84 subprocess.check_call(["git", "commit", "-q", "-m", "init"], cwd=git_dir)
85
86
87def _run_status(manifest: manifest_xml.XmlManifest, argv: List[str]) -> None:
88 """Run the status subcommand with parsed options against a test manifest."""
89 cmd = subcmds.status.Status()
90 cmd.manifest = manifest
91 cmd.client = mock.MagicMock(globalConfig=manifest.globalConfig)
92
93 opts, args = cmd.OptionParser.parse_args(argv + ["--jobs=1"])
94 cmd.CommonValidateOptions(opts, args)
95
96 cmd.Execute(opts, args)
97
98
99def _status_lines(output: str) -> List[str]:
100 """Normalize path separators and split command output into lines."""
101 return output.replace(os.sep, "/").splitlines()
102
103
104def _assert_project_header(line: str, project_path: str, branch: str) -> None:
105 """Assert a status project header line for a project and branch."""
106 expected = f"project {(project_path + '/ '):<40}branch {branch}"
107 assert line == expected
108
109
110def _assert_orphan_block(lines: List[str], expected: List[str]) -> None:
111 """Assert orphan block header and entries, independent of entry ordering."""
112 assert lines
113 assert lines[0] == ("Objects not within a project (orphans)")
114 orphan_lines = lines[1:]
115 assert len(orphan_lines) == len(expected)
116 assert sorted(orphan_lines) == sorted(expected)
117
118
119def test_orphans_basic(
120 repo_client_checkout: Tuple[Path, manifest_xml.XmlManifest],
121) -> None:
122 """Verify -o output includes project header and orphan block."""
123 topdir, manifest = repo_client_checkout
124 project_path = next(iter(manifest.paths.keys()))
125
126 (topdir / "src" / "orphan_dir").mkdir(parents=True)
127 (topdir / "orphan.txt").write_text("data")
128
129 with contextlib.redirect_stdout(io.StringIO()) as stdout:
130 _run_status(manifest, ["-o"])
131
132 lines = _status_lines(stdout.getvalue())
133 _assert_project_header(lines[0], project_path, "main")
134 _assert_orphan_block(
135 lines[1:],
136 [
137 " --\torphan.txt",
138 " --\tsrc/orphan_dir/",
139 ],
140 )
141
142
143def test_empty_status_without_orphans(
144 repo_client_checkout: Tuple[Path, manifest_xml.XmlManifest],
145) -> None:
146 """Verify clean status without -o prints only the project header line."""
147 _, manifest = repo_client_checkout
148 project_path = next(iter(manifest.paths.keys()))
149
150 with contextlib.redirect_stdout(io.StringIO()) as stdout:
151 _run_status(manifest, [])
152
153 lines = _status_lines(stdout.getvalue())
154 assert len(lines) == 1
155 _assert_project_header(lines[0], project_path, "main")
156
157
158def test_status_without_orphans(
159 repo_client_checkout: Tuple[Path, manifest_xml.XmlManifest],
160) -> None:
161 """Verify modified tracked file appears in status output without -o."""
162 topdir, manifest = repo_client_checkout
163 project_path = next(iter(manifest.paths.keys()))
164
165 (topdir / project_path / "README").write_text("updated")
166
167 with contextlib.redirect_stdout(io.StringIO()) as stdout:
168 _run_status(manifest, [])
169
170 lines = _status_lines(stdout.getvalue())
171 assert len(lines) == 2
172 _assert_project_header(lines[0], project_path, "main")
173 assert lines[1] == " -m\tREADME"
174
175
176def test_status_with_orphans_and_modified_file(
177 repo_client_checkout: Tuple[Path, manifest_xml.XmlManifest],
178) -> None:
179 """Verify modified-file status plus orphan block."""
180 topdir, manifest = repo_client_checkout
181 project_path = next(iter(manifest.paths.keys()))
182
183 (topdir / project_path / "README").write_text("updated")
184 (topdir / "src" / "orphan_dir").mkdir(parents=True)
185 (topdir / "orphan.txt").write_text("data")
186
187 with contextlib.redirect_stdout(io.StringIO()) as stdout:
188 _run_status(manifest, ["-o"])
189
190 lines = _status_lines(stdout.getvalue())
191 _assert_project_header(lines[0], project_path, "main")
192 assert lines[1] == " -m\tREADME"
193 _assert_orphan_block(
194 lines[2:],
195 [
196 " --\torphan.txt",
197 " --\tsrc/orphan_dir/",
198 ],
199 )
200
201
202def test_empty_status_after_start_shows_started_branch(
203 repo_client_checkout: Tuple[Path, manifest_xml.XmlManifest],
204) -> None:
205 """Verify status shows the started branch name when the tree is clean."""
206 topdir, manifest = repo_client_checkout
207
208 project_path = next(iter(manifest.paths.keys()))
209 project_worktree = topdir / project_path
210 started_branch = "topic/test-status-branch"
211 subprocess.check_call(
212 ["git", "checkout", "-q", "-b", started_branch], cwd=project_worktree
213 )
214
215 with contextlib.redirect_stdout(io.StringIO()) as stdout:
216 _run_status(manifest, [])
217
218 lines = _status_lines(stdout.getvalue())
219 assert len(lines) == 1
220 _assert_project_header(lines[0], project_path, started_branch)