Skip to content

Commit 95b6079

Browse files
committed
Merge branch 'topic/ci-fixes' (CI green-keeping fixes)
2 parents 59148ed + 394ef5a commit 95b6079

9 files changed

Lines changed: 350 additions & 190 deletions

File tree

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -811,6 +811,7 @@ TEST_BUILTINS_OBJS += test-csprng.o
811811
TEST_BUILTINS_OBJS += test-date.o
812812
TEST_BUILTINS_OBJS += test-delete-gpgsig.o
813813
TEST_BUILTINS_OBJS += test-delta.o
814+
TEST_BUILTINS_OBJS += test-diff-process-backend.o
814815
TEST_BUILTINS_OBJS += test-dir-iterator.o
815816
TEST_BUILTINS_OBJS += test-drop-caches.o
816817
TEST_BUILTINS_OBJS += test-dump-cache-tree.o

t/helper/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ test_tool_sources = [
1212
'test-date.c',
1313
'test-delete-gpgsig.c',
1414
'test-delta.c',
15+
'test-diff-process-backend.c',
1516
'test-dir-iterator.c',
1617
'test-drop-caches.c',
1718
'test-dump-cache-tree.c',
Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
/*
2+
* Test backend for the long-running diff process protocol
3+
* (see diff-process.c and Documentation/gitattributes.adoc).
4+
*
5+
* Usage: test-tool diff-process-backend --mode=<mode> [--log=<path>]
6+
*
7+
* Implements the server side of the handshake
8+
*
9+
* git> git-diff-client / version=1 / flush
10+
* tool< git-diff-server / version=1 / flush
11+
* git> capability=hunks / flush
12+
* tool< capability=hunks / flush
13+
*
14+
* and a per-file response loop driven by a single --mode= switch so
15+
* that t4080-diff-process.sh can exercise every documented branch
16+
* (success, error, abort, crash, malformed hunks) from a shell test
17+
* without having to depend on Python or Perl.
18+
*
19+
* Each request from Git looks like
20+
*
21+
* git> command=hunks / pathname=<path> / flush
22+
* git> <old content packetized> / flush
23+
* git> <new content packetized> / flush
24+
*
25+
* and the response chosen by --mode is
26+
*
27+
* whole-file (default) hunk 1 <old-lines> 1 <new-lines>; status=success
28+
* fixed-hunk hunk 5 2 5 2; status=success
29+
* no-hunks (no hunk lines); status=success
30+
* bad-hunk hunk 999 1 999 1; status=success
31+
* bad-sync hunk 1 2 1 1; status=success
32+
* overlap hunk 1 5 1 5 + hunk 3 2 3 2; status=success
33+
* error (no hunk lines); status=error
34+
* abort (no hunk lines); status=abort
35+
* crash exit(1) after reading the request
36+
*
37+
* Each request is summarised in the --log file as
38+
*
39+
* command=<cmd> pathname=<path> old=<first line> new=<first line>
40+
*
41+
* which is what t4080's test_grep assertions key off of.
42+
*/
43+
44+
#include "test-tool.h"
45+
#include "pkt-line.h"
46+
#include "parse-options.h"
47+
#include "strbuf.h"
48+
49+
static FILE *logfile;
50+
51+
enum mode {
52+
MODE_WHOLE_FILE,
53+
MODE_FIXED_HUNK,
54+
MODE_NO_HUNKS,
55+
MODE_BAD_HUNK,
56+
MODE_BAD_SYNC,
57+
MODE_OVERLAP,
58+
MODE_ERROR,
59+
MODE_ABORT,
60+
MODE_CRASH,
61+
};
62+
63+
static enum mode parse_mode(const char *s)
64+
{
65+
if (!strcmp(s, "whole-file")) return MODE_WHOLE_FILE;
66+
if (!strcmp(s, "fixed-hunk")) return MODE_FIXED_HUNK;
67+
if (!strcmp(s, "no-hunks")) return MODE_NO_HUNKS;
68+
if (!strcmp(s, "bad-hunk")) return MODE_BAD_HUNK;
69+
if (!strcmp(s, "bad-sync")) return MODE_BAD_SYNC;
70+
if (!strcmp(s, "overlap")) return MODE_OVERLAP;
71+
if (!strcmp(s, "error")) return MODE_ERROR;
72+
if (!strcmp(s, "abort")) return MODE_ABORT;
73+
if (!strcmp(s, "crash")) return MODE_CRASH;
74+
die("unknown --mode=%s", s);
75+
}
76+
77+
/*
78+
* Drain "key=value" packets up to the next flush, capturing the
79+
* "command" and "pathname" values. Any other key is accepted and
80+
* ignored so the helper stays forward-compatible with future Git
81+
* versions that may add per-request metadata.
82+
*
83+
* Returns 1 if a request header was read, 0 on EOF (Git closed
84+
* its end and is shutting down the long-running process).
85+
*/
86+
static int read_request_header(char **command, char **pathname)
87+
{
88+
int first = 1;
89+
char *line;
90+
91+
*command = *pathname = NULL;
92+
for (;;) {
93+
int len;
94+
const char *value;
95+
96+
/*
97+
* Use the gentle variant for the first packet only so
98+
* that a clean shutdown by Git does not produce a
99+
* spurious "the remote end hung up unexpectedly" on
100+
* stderr. Subsequent packets up to the flush are
101+
* guaranteed to be present, so the non-gentle variant
102+
* (which dies on truncation) is the right tool to
103+
* catch a misbehaving Git there.
104+
*/
105+
if (first) {
106+
len = packet_read_line_gently(0, NULL, &line);
107+
first = 0;
108+
if (len < 0)
109+
return 0;
110+
} else {
111+
line = packet_read_line(0, NULL);
112+
}
113+
if (!line)
114+
break;
115+
if (skip_prefix(line, "command=", &value))
116+
*command = xstrdup(value);
117+
else if (skip_prefix(line, "pathname=", &value))
118+
*pathname = xstrdup(value);
119+
}
120+
return 1;
121+
}
122+
123+
static size_t count_lines(const struct strbuf *buf)
124+
{
125+
size_t lines = 0;
126+
127+
for (size_t i = 0; i < buf->len; i++)
128+
if (buf->buf[i] == '\n')
129+
lines++;
130+
131+
return lines + (buf->len > 0 && buf->buf[buf->len - 1] != '\n');
132+
}
133+
134+
static void send_status(const char *status)
135+
{
136+
packet_flush(1);
137+
packet_write_fmt(1, "%s\n", status);
138+
packet_flush(1);
139+
}
140+
141+
static void respond(enum mode mode,
142+
const struct strbuf *old_buf,
143+
const struct strbuf *new_buf)
144+
{
145+
switch (mode) {
146+
case MODE_ERROR:
147+
send_status("status=error");
148+
return;
149+
case MODE_ABORT:
150+
send_status("status=abort");
151+
return;
152+
case MODE_CRASH:
153+
exit(1);
154+
case MODE_FIXED_HUNK:
155+
packet_write_fmt(1, "hunk 5 2 5 2\n");
156+
break;
157+
case MODE_BAD_HUNK:
158+
packet_write_fmt(1, "hunk 999 1 999 1\n");
159+
break;
160+
case MODE_BAD_SYNC:
161+
packet_write_fmt(1, "hunk 1 2 1 1\n");
162+
break;
163+
case MODE_OVERLAP:
164+
packet_write_fmt(1, "hunk 1 5 1 5\n");
165+
packet_write_fmt(1, "hunk 3 2 3 2\n");
166+
break;
167+
case MODE_NO_HUNKS:
168+
break;
169+
case MODE_WHOLE_FILE:
170+
packet_write_fmt(1, "hunk 1 %"PRIuMAX" 1 %"PRIuMAX"\n",
171+
(uintmax_t)count_lines(old_buf),
172+
(uintmax_t)count_lines(new_buf));
173+
break;
174+
}
175+
send_status("status=success");
176+
}
177+
178+
static void command_loop(enum mode mode)
179+
{
180+
for (;;) {
181+
char *command = NULL, *pathname = NULL;
182+
struct strbuf obuf = STRBUF_INIT;
183+
struct strbuf nbuf = STRBUF_INIT;
184+
185+
if (!read_request_header(&command, &pathname))
186+
break; /* EOF: Git closed its end */
187+
188+
read_packetized_to_strbuf(0, &obuf, 0);
189+
read_packetized_to_strbuf(0, &nbuf, 0);
190+
191+
if (logfile) {
192+
fprintf(logfile,
193+
"command=%s pathname=%s old=%.*s new=%.*s\n",
194+
command ? command : "(none)",
195+
pathname ? pathname : "(none)",
196+
(int)(strchrnul(obuf.buf, '\n') - obuf.buf),
197+
obuf.buf,
198+
(int)(strchrnul(nbuf.buf, '\n') - nbuf.buf),
199+
nbuf.buf);
200+
fflush(logfile);
201+
}
202+
203+
respond(mode, &obuf, &nbuf);
204+
205+
free(command);
206+
free(pathname);
207+
strbuf_release(&obuf);
208+
strbuf_release(&nbuf);
209+
}
210+
}
211+
212+
static void handshake(void)
213+
{
214+
char *line;
215+
216+
line = packet_read_line(0, NULL);
217+
if (!line || strcmp(line, "git-diff-client"))
218+
die("bad welcome: '%s'", line ? line : "(eof)");
219+
line = packet_read_line(0, NULL);
220+
if (!line || strcmp(line, "version=1"))
221+
die("bad version: '%s'", line ? line : "(eof)");
222+
if (packet_read_line(0, NULL))
223+
die("expected flush after version");
224+
225+
packet_write_fmt(1, "git-diff-server\n");
226+
packet_write_fmt(1, "version=1\n");
227+
packet_flush(1);
228+
229+
/*
230+
* Git advertises capabilities and we acknowledge the ones we
231+
* support. This backend always claims "hunks"; unknown
232+
* capabilities are silently dropped.
233+
*/
234+
while ((line = packet_read_line(0, NULL)))
235+
; /* drain */
236+
packet_write_fmt(1, "capability=hunks\n");
237+
packet_flush(1);
238+
}
239+
240+
static const char *const usage_str[] = {
241+
"test-tool diff-process-backend --mode=<mode> [--log=<path>]",
242+
NULL
243+
};
244+
245+
int cmd__diff_process_backend(int argc, const char **argv)
246+
{
247+
const char *mode_str = NULL, *log_path = NULL;
248+
enum mode mode = MODE_WHOLE_FILE;
249+
struct option options[] = {
250+
OPT_STRING(0, "mode", &mode_str, "mode",
251+
"response shape: whole-file (default), fixed-hunk,"
252+
" no-hunks, bad-hunk, bad-sync, overlap, error,"
253+
" abort, crash"),
254+
OPT_STRING(0, "log", &log_path, "path",
255+
"append per-request summary to this file"),
256+
OPT_END()
257+
};
258+
259+
argc = parse_options(argc, argv, NULL, options, usage_str, 0);
260+
if (argc)
261+
usage_with_options(usage_str, options);
262+
263+
if (mode_str)
264+
mode = parse_mode(mode_str);
265+
266+
if (log_path) {
267+
logfile = fopen(log_path, "a");
268+
if (!logfile)
269+
die_errno("failed to open log '%s'", log_path);
270+
}
271+
272+
handshake();
273+
command_loop(mode);
274+
275+
if (logfile && fclose(logfile))
276+
die_errno("error closing log");
277+
return 0;
278+
}

t/helper/test-tool.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ static struct test_cmd cmds[] = {
2222
{ "date", cmd__date },
2323
{ "delete-gpgsig", cmd__delete_gpgsig },
2424
{ "delta", cmd__delta },
25+
{ "diff-process-backend", cmd__diff_process_backend },
2526
{ "dir-iterator", cmd__dir_iterator },
2627
{ "drop-caches", cmd__drop_caches },
2728
{ "dump-cache-tree", cmd__dump_cache_tree },

t/helper/test-tool.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ int cmd__csprng(int argc, const char **argv);
1515
int cmd__date(int argc, const char **argv);
1616
int cmd__delta(int argc, const char **argv);
1717
int cmd__delete_gpgsig(int argc, const char **argv);
18+
int cmd__diff_process_backend(int argc, const char **argv);
1819
int cmd__dir_iterator(int argc, const char **argv);
1920
int cmd__drop_caches(int argc, const char **argv);
2021
int cmd__dump_cache_tree(int argc, const char **argv);

0 commit comments

Comments
 (0)