patch-via-github 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ """Test suite for patch_via_github."""
@@ -0,0 +1,185 @@
1
+ import os
2
+ import subprocess
3
+ import xml.etree.ElementTree as EleTree
4
+ from pathlib import Path
5
+
6
+ import pytest
7
+ import requests
8
+
9
+ import patch_via_github.scripts.main as app
10
+
11
+ TESTS_DIR = Path(__file__).parent
12
+
13
+ GIT_ENV = {
14
+ 'GIT_AUTHOR_NAME': 't', 'GIT_AUTHOR_EMAIL': 't@example.com',
15
+ 'GIT_COMMITTER_NAME': 't', 'GIT_COMMITTER_EMAIL': 't@example.com',
16
+ 'GIT_CONFIG_GLOBAL': os.devnull, 'GIT_CONFIG_NOSYSTEM': '1',
17
+ }
18
+
19
+
20
+ @pytest.fixture(autouse=True)
21
+ def isolated(monkeypatch, tmp_path):
22
+ """No network, no host config, and no logger state leaking between tests"""
23
+
24
+ def no_network(*args, **kwargs):
25
+ pytest.fail(f'test attempted network access: {args[1:3]}')
26
+
27
+ monkeypatch.setattr(requests.Session, 'request', no_network)
28
+ # Git may only reach local repos, so a wrong fetch URL can't hit GitHub
29
+ monkeypatch.setenv('GIT_ALLOW_PROTOCOL', 'file')
30
+ monkeypatch.setenv('HOME', str(tmp_path / 'home'))
31
+ monkeypatch.delenv('LD_LIBRARY_PATH', raising=False)
32
+ handlers, level = app.logger.handlers[:], app.logger.level
33
+ yield
34
+ app.logger.handlers[:] = handlers
35
+ app.logger.setLevel(level)
36
+
37
+
38
+ def make_pr_api_response(
39
+ number, repo_name, org="couchbase", base_ref="master",
40
+ head_sha="abc123", state="open", labels=None, title=None
41
+ ):
42
+ """Build a mock GitHub PR API response dict."""
43
+ return {
44
+ "number": number,
45
+ "state": state,
46
+ "title": title or f"PR #{number}",
47
+ "html_url": f"https://github.com/{org}/{repo_name}/pull/{number}",
48
+ "labels": [{"name": lbl} for lbl in (labels or [])],
49
+ "base": {
50
+ "ref": base_ref,
51
+ "repo": {
52
+ "name": repo_name,
53
+ "full_name": f"{org}/{repo_name}",
54
+ "clone_url": f"https://github.com/{org}/{repo_name}.git",
55
+ "ssh_url": f"git@github.com:{org}/{repo_name}.git",
56
+ },
57
+ },
58
+ "head": {
59
+ "sha": head_sha,
60
+ "ref": f"feature-{number}",
61
+ "repo": {
62
+ "clone_url": f"https://github.com/{org}/{repo_name}.git",
63
+ },
64
+ },
65
+ }
66
+
67
+
68
+ def commit(sha, parents):
69
+ return {"sha": sha, "parents": [{"sha": p} for p in parents]}
70
+
71
+
72
+ @pytest.fixture
73
+ def config_file(tmp_path):
74
+ ini = tmp_path / "patch_via_github.ini"
75
+ ini.write_text(
76
+ "[main]\n"
77
+ "token = ghp_test_token_000000000000000000000\n"
78
+ "default_org = couchbase\n"
79
+ )
80
+ return str(ini)
81
+
82
+
83
+ @pytest.fixture
84
+ def config_file_no_org(tmp_path):
85
+ ini = tmp_path / "patch_via_github_no_org.ini"
86
+ ini.write_text(
87
+ "[main]\n"
88
+ "token = ghp_test_token_000000000000000000000\n"
89
+ )
90
+ return str(ini)
91
+
92
+
93
+ @pytest.fixture
94
+ def gp_with_manifest(config_file):
95
+ gp = app.GitHubPatches.from_config_file(config_file)
96
+ gp.manifest = EleTree.parse(str(TESTS_DIR / "manifest.xml")).getroot()
97
+ return gp
98
+
99
+
100
+ class GitRepos:
101
+ """
102
+ An origin repo exposing PR heads as refs/pull/N/head, a clone of it
103
+ standing in for a repo sync project, and the GitHub API data for the
104
+ PRs made on it
105
+ """
106
+
107
+ def __init__(self, root):
108
+ self.root = root
109
+ self.origin = root / "origin"
110
+ self.work = root / "work"
111
+ self.git(root, 'init', '-q', '-b', 'master', str(self.origin))
112
+ self.commit_file("f", "base\n", "base")
113
+ self.git(root, 'clone', '-q', str(self.origin), str(self.work))
114
+ self.api = {}
115
+ self.endpoints = []
116
+
117
+ def git(self, cwd, *args):
118
+ return subprocess.run(
119
+ ['git', *args], cwd=cwd, check=True, capture_output=True,
120
+ text=True, env={**os.environ, **GIT_ENV}
121
+ ).stdout.strip()
122
+
123
+ def clone(self, path):
124
+ self.git(self.root, 'clone', '-q', str(self.origin), str(path))
125
+ return path
126
+
127
+ def commit_file(self, name, content, msg, repo=None):
128
+ repo = repo or self.origin
129
+ (repo / name).write_text(content)
130
+ self.git(repo, 'add', name)
131
+ self.git(repo, 'commit', '-q', '-m', msg)
132
+ return self.git(repo, 'rev-parse', 'HEAD')
133
+
134
+ def make_pr(self, number, branch, commits, start='master',
135
+ project='work', **fields):
136
+ """commits: [(file, content, message)]; None content = empty commit"""
137
+ self.git(self.origin, 'checkout', '-q', '-B', branch, start)
138
+ for name, content, msg in commits:
139
+ if content is None:
140
+ self.git(self.origin, 'commit', '-q', '--allow-empty',
141
+ '-m', msg)
142
+ else:
143
+ self.commit_file(name, content, msg)
144
+ head = self.git(self.origin, 'rev-parse', 'HEAD')
145
+ self.git(self.origin, 'update-ref', f'refs/pull/{number}/head', head)
146
+ self.git(self.origin, 'checkout', '-q', 'master')
147
+
148
+ log = self.git(self.origin, 'log', '--format=%H %P',
149
+ f'{start}..{head}').splitlines()
150
+ data = make_pr_api_response(number, project, head_sha=head, **fields)
151
+ data['base']['repo']['clone_url'] = str(self.origin)
152
+ data['base']['repo']['ssh_url'] = str(self.root / "ssh-unreachable")
153
+ api_commits = [commit(line.split()[0], line.split()[1:])
154
+ for line in log]
155
+
156
+ # The listing omits the commit count, as GitHub's does
157
+ pulls = f'/repos/couchbase/{project}/pulls'
158
+ self.api[f'{pulls}/{number}'] = {**data, 'commits': len(log)}
159
+ self.api[f'{pulls}/{number}/commits?per_page=100'] = api_commits
160
+ listing = self.api.setdefault(f'{pulls}?state=open&per_page=100', [])
161
+ listing[:] = [pr for pr in listing if pr['number'] != number]
162
+ if data['state'] == 'open':
163
+ listing.append(data)
164
+
165
+ pr = app.GitHubPR({**data, 'commits': len(log)})
166
+ return pr, api_commits
167
+
168
+ def api_get(self, endpoint):
169
+ """Stand-in for GitHub; names are case-insensitive, as there"""
170
+ self.endpoints.append(endpoint)
171
+ return self.api[endpoint.lower()]
172
+
173
+ def work_log(self, repo=None):
174
+ return self.git(repo or self.work, 'log',
175
+ '--format=%s').splitlines()
176
+
177
+ def work_file(self, name):
178
+ return (self.work / name).read_text()
179
+
180
+
181
+ @pytest.fixture
182
+ def repos(tmp_path, monkeypatch):
183
+ for key, value in GIT_ENV.items():
184
+ monkeypatch.setenv(key, value)
185
+ return GitRepos(tmp_path)
@@ -0,0 +1,18 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <manifest>
3
+ <!-- Test-only manifest for patch_via_github unit tests. Never built. -->
4
+ <remote name="couchbase" fetch="https://github.com/couchbase/" />
5
+
6
+ <default remote="couchbase" revision="master" />
7
+
8
+ <project name="tlm" revision="master" groups="kv"/>
9
+ <project name="ns_server" groups="bsl"/>
10
+ <project name="geocouch" revision="3e8040787139e3c67766ce50398860c185737b8a"/>
11
+ <project name="eventing" path="goproj/src/github.com/couchbase/eventing"/>
12
+ <project name="build" groups="build"/>
13
+ <project name="server-ui" revision="main"/>
14
+ <project name="couchbase-cli" path="couchbase-cli-tools" revision="master"/>
15
+ <project name="cbgt"/>
16
+ <project name="stellar-gateway" revision="a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2"/>
17
+
18
+ </manifest>