Nilorea Library
C utilities for networking, threading, graphics
Loading...
Searching...
No Matches
ex_diff.c
Go to the documentation of this file.
1/*
2 * Nilorea Library
3 * Copyright (C) 2005-2026 Castagnier Mickael
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14 * implied. See the License for the specific language governing
15 * permissions and limitations under the License.
16 *
17 * SPDX-License-Identifier: Apache-2.0
18 */
19
26
27#include "nilorea/n_common.h"
28#include "nilorea/n_diff.h"
29#include "nilorea/n_log.h"
30#include "nilorea/n_str.h"
31
32#include <stdio.h>
33#include <string.h>
34#include <unistd.h>
35
36static int failures = 0;
37
38/* Parse the -V LOG_LEVEL verbosity argument. */
39static void process_args(int argc, char** argv) {
40 int opt = 0;
41 while ((opt = getopt(argc, argv, "hvV:")) != EOF) {
42 switch (opt) {
43 case 'V':
44 if (!strncmp("LOG_NULL", optarg, 8))
46 else if (!strncmp("LOG_NOTICE", optarg, 10))
48 else if (!strncmp("LOG_INFO", optarg, 8))
50 else if (!strncmp("LOG_ERR", optarg, 7))
52 else if (!strncmp("LOG_DEBUG", optarg, 9))
54 else {
55 fprintf(stderr, "Unknown log level %s\n", optarg);
56 exit(1);
57 }
58 break;
59 case 'v':
60 fprintf(stderr, "ex_diff\n");
61 exit(1);
62 default:
63 fprintf(stderr, "Usage: %s [-V LOG_LEVEL]\n", argv[0]);
64 exit(1);
65 }
66 }
67}
68
69static void expect_true(const char* label, int cond) {
70 if (!cond) {
71 n_log(LOG_ERR, "%s: condition false", label);
72 failures++;
73 }
74}
75
76static void expect_size(const char* label, size_t got, size_t want) {
77 if (got != want) {
78 n_log(LOG_ERR, "%s: got %zu, expected %zu", label, got, want);
79 failures++;
80 }
81}
82
83static void expect_contains(const char* label, const N_STR* out, const char* needle) {
84 if (!out || !out->data || !strstr(out->data, needle)) {
85 n_log(LOG_ERR, "%s: output does not contain '%s' (got: %s)",
86 label, needle, (out && out->data) ? out->data : "NULL");
87 failures++;
88 }
89}
90
91static void expect_missing(const char* label, const N_STR* out, const char* needle) {
92 if (!out || !out->data || strstr(out->data, needle)) {
93 n_log(LOG_ERR, "%s: output should not contain '%s' (got: %s)",
94 label, needle, (out && out->data) ? out->data : "NULL");
95 failures++;
96 }
97}
98
99/* Render a script with full context and check it against an expected text. */
100static void expect_unified(const char* label, const char* a, const char* b, const char* want) {
101 N_DIFF_RESULT* res = n_diff_lines(a, b, 0);
102 N_STR* out = NULL;
103
104 if (!res) {
105 n_log(LOG_ERR, "%s: n_diff_lines returned NULL", label);
106 failures++;
107 return;
108 }
110 if (!out || !out->data || strcmp(out->data, want) != 0) {
111 n_log(LOG_ERR, "%s: got [%s], expected [%s]", label,
112 (out && out->data) ? out->data : "NULL", want);
113 failures++;
114 }
115 free_nstr(&out);
116 n_diff_result_free(&res);
117}
118
119int main(int argc, char** argv) {
121 process_args(argc, argv);
122
123 /* identical texts produce common lines only */
124 {
125 N_DIFF_RESULT* res = n_diff_lines("a\nb\nc", "a\nb\nc", 0);
126 expect_true("identical returns a result", res != NULL);
127 expect_size("identical common count", res->nb_common, 3);
128 expect_size("identical delete count", res->nb_deleted, 0);
129 expect_size("identical insert count", res->nb_inserted, 0);
130 expect_true("identical not truncated", res->truncated == 0);
131 n_diff_result_free(&res);
132 }
133
134 /* a trailing newline closes the last line, it does not open an empty one */
135 {
136 N_DIFF_RESULT* res = n_diff_lines("a\nb\n", "a\nb", 0);
137 expect_size("trailing newline common count", res->nb_common, 2);
138 expect_size("trailing newline no change", res->nb_deleted + res->nb_inserted, 0);
139 n_diff_result_free(&res);
140 }
141
142 /* the point of an LCS: one inserted line must not shift everything after
143 it into a run of changes */
144 {
145 N_DIFF_RESULT* res = n_diff_lines("a\nb\nc\nd", "a\nx\nb\nc\nd", 0);
146 expect_size("insert keeps 4 common", res->nb_common, 4);
147 expect_size("insert adds 1", res->nb_inserted, 1);
148 expect_size("insert deletes nothing", res->nb_deleted, 0);
149 n_diff_result_free(&res);
150 }
151 expect_unified("inserted line rendering", "a\nb\nc\nd", "a\nx\nb\nc\nd",
152 " a\n+x\n b\n c\n d\n");
153
154 /* deletion, replacement and edits at both ends */
155 expect_unified("deleted line", "a\nb\nc", "a\nc", " a\n-b\n c\n");
156 expect_unified("replaced line", "a\nb\nc", "a\nB\nc", " a\n-b\n+B\n c\n");
157 expect_unified("edit at head", "a\nb", "x\nb", "-a\n+x\n b\n");
158 expect_unified("edit at tail", "a\nb", "a\nx", " a\n-b\n+x\n");
159
160 /* empty sides */
161 expect_unified("empty to content", "", "a\nb", "+a\n+b\n");
162 expect_unified("content to empty", "a\nb", "", "-a\n-b\n");
163 {
164 N_DIFF_RESULT* res = n_diff_lines(NULL, NULL, 0);
165 expect_true("NULL inputs return a result", res != NULL);
166 expect_size("NULL inputs give no lines", res->lines->nb_items, 0);
167 n_diff_result_free(&res);
168
169 res = n_diff_lines("", "", 0);
170 expect_size("empty inputs give no lines", res->lines->nb_items, 0);
171 n_diff_result_free(&res);
172 }
173
174 /* carriage returns are content, not noise: LF -> CRLF is a real change */
175 {
176 N_DIFF_RESULT* res = n_diff_lines("a\nb", "a\r\nb", 0);
177 expect_size("CRLF differs from LF", res->nb_deleted, 1);
178 n_diff_result_free(&res);
179 }
180
181 /* line numbers are positions in the whole text, past the trimmed head */
182 {
183 N_DIFF_RESULT* res = n_diff_lines("a\nb\nc\nd", "a\nb\nX\nd", 0);
184 int checked = 0;
185 list_foreach(node, res->lines) {
186 N_DIFF_LINE* line = (N_DIFF_LINE*)node->ptr;
187 if (line->op == N_DIFF_DELETE) {
188 expect_size("delete line_a", line->line_a, 2);
189 expect_true("delete has no line_b", line->line_b == N_DIFF_NO_LINE);
190 checked++;
191 } else if (line->op == N_DIFF_INSERT) {
192 expect_size("insert line_b", line->line_b, 2);
193 expect_true("insert has no line_a", line->line_a == N_DIFF_NO_LINE);
194 checked++;
195 }
196 }
197 expect_size("both changed lines inspected", (size_t)checked, 2);
198 n_diff_result_free(&res);
199 }
200
201 /* hunk rendering drops common lines further than context from a change */
202 {
203 N_DIFF_RESULT* res = n_diff_lines("1\n2\n3\n4\n5\n6\n7\n8\n9",
204 "1\n2\n3\n4\nX\n6\n7\n8\n9", 0);
205 N_STR* out = n_diff_to_unified(res, 1);
206 expect_contains("hunk header present", out, "@@ -4,3 +4,3 @@");
207 expect_contains("hunk keeps one line before", out, " 4\n");
208 expect_contains("hunk shows the change", out, "-5\n+X\n");
209 expect_contains("hunk keeps one line after", out, " 6\n");
210 expect_missing("hunk drops distant head", out, " 1\n");
211 expect_missing("hunk drops distant tail", out, " 9\n");
212 free_nstr(&out);
213
214 /* full context keeps everything and writes no header */
216 expect_contains("full context keeps head", out, " 1\n");
217 expect_contains("full context keeps tail", out, " 9\n");
218 expect_missing("full context has no header", out, "@@");
219 free_nstr(&out);
220 n_diff_result_free(&res);
221 }
222
223 /* two changes separated by no more than 2*context common lines merge
224 into a single hunk, like diff(1) does */
225 {
226 N_DIFF_RESULT* res = n_diff_lines("1\n2\n3\n4", "X\n2\n3\nY", 0);
227 N_STR* out = n_diff_to_unified(res, 1);
228 int hunks = 0;
229 for (const char* p = out->data; (p = strstr(p, "@@ -")) != NULL; p++) {
230 hunks++;
231 }
232 expect_size("nearby changes merge", (size_t)hunks, 1);
233 free_nstr(&out);
234 n_diff_result_free(&res);
235 }
236
237 /* distant changes stay in separate hunks */
238 {
239 N_DIFF_RESULT* res = n_diff_lines("1\n2\n3\n4\n5\n6\n7\n8\n9",
240 "X\n2\n3\n4\n5\n6\n7\n8\nY", 0);
241 N_STR* out = n_diff_to_unified(res, 1);
242 int hunks = 0;
243 for (const char* p = out->data; (p = strstr(p, "@@ -")) != NULL; p++) {
244 hunks++;
245 }
246 expect_size("distant changes split", (size_t)hunks, 2);
247 free_nstr(&out);
248 n_diff_result_free(&res);
249 }
250
251 /* past the edit ceiling the whole differing region is reported coarsely,
252 and the result says so instead of pretending to be exact */
253 {
254 N_STR* a = new_nstr(1024);
255 N_STR* b = new_nstr(1024);
256 N_DIFF_RESULT* res = NULL;
257
258 for (int i = 0; i < 40; i++) {
259 nstrprintf_cat(a, "line a %d\n", i);
260 nstrprintf_cat(b, "line b %d\n", i);
261 }
262 res = n_diff_lines(_nstr(a), _nstr(b), 4);
263 expect_true("over ceiling returns a result", res != NULL);
264 expect_true("over ceiling is flagged", res->truncated == 1);
265 expect_size("over ceiling deletes every a line", res->nb_deleted, 40);
266 expect_size("over ceiling inserts every b line", res->nb_inserted, 40);
267 expect_size("over ceiling keeps nothing common", res->nb_common, 0);
268 n_diff_result_free(&res);
269
270 /* the same pair within a generous ceiling is exact */
271 res = n_diff_lines(_nstr(a), _nstr(b), 0);
272 expect_true("under ceiling is not flagged", res->truncated == 0);
273 n_diff_result_free(&res);
274
275 free_nstr(&a);
276 free_nstr(&b);
277 }
278
279 /* a small change inside two large identical bodies stays well under the
280 ceiling, because the matching head and tail are trimmed first */
281 {
282 N_STR* a = new_nstr(65536);
283 N_STR* b = new_nstr(65536);
284 N_DIFF_RESULT* res = NULL;
285
286 for (int i = 0; i < 5000; i++) {
287 nstrprintf_cat(a, "shared line %d\n", i);
288 nstrprintf_cat(b, "shared line %d\n", i == 2500 ? -1 : i);
289 }
290 res = n_diff_lines(_nstr(a), _nstr(b), 0);
291 expect_true("large bodies not truncated", res->truncated == 0);
292 expect_size("large bodies one delete", res->nb_deleted, 1);
293 expect_size("large bodies one insert", res->nb_inserted, 1);
294 expect_size("large bodies keep the rest", res->nb_common, 4999);
295 n_diff_result_free(&res);
296
297 free_nstr(&a);
298 free_nstr(&b);
299 }
300
301 /* rendering an empty script is an empty string, not a failure */
302 {
303 N_DIFF_RESULT* res = n_diff_lines("", "", 0);
304 N_STR* out = n_diff_to_unified(res, 3);
305 expect_true("empty script renders", out != NULL);
306 expect_size("empty script is empty", out->written, 0);
307 free_nstr(&out);
308 n_diff_result_free(&res);
309 }
310
311 if (failures) {
312 n_log(LOG_ERR, "ex_diff: %d failure(s)", failures);
313 return 1;
314 }
315 printf("ex_diff: all tests passed\n");
316 return 0;
317}
void process_args(int argc, char **argv)
static int failures
Definition ex_atlas.c:51
int main(void)
Definition ex_atlas.c:117
static void expect_true(const char *label, int cond)
Definition ex_atlas.c:53
static void expect_unified(const char *label, const char *a, const char *b, const char *want)
Definition ex_diff.c:100
static void expect_size(const char *label, size_t got, size_t want)
Definition ex_diff.c:76
static void expect_contains(const char *label, const N_STR *out, const char *needle)
Definition ex_diff.c:83
static void expect_missing(const char *label, const N_STR *out, const char *needle)
Definition ex_diff.c:91
void line(BITMAP *bmp, int x1, int y1, int x2, int y2, int color)
draw a line
#define _nstr(__PTR)
N_STR or "NULL" string for logging purposes.
Definition n_common.h:199
size_t nb_items
number of item currently in the list
Definition n_list.h:61
#define list_foreach(__ITEM_, __LIST_)
ForEach macro helper, safe for node removal during iteration.
Definition n_list.h:89
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
Definition n_log.h:113
#define LOG_DEBUG
debug-level messages
Definition n_log.h:108
#define LOG_ERR
error conditions
Definition n_log.h:100
void set_log_level(const int log_level)
Set the global log level value ( static int LOG_LEVEL ).
Definition n_log.c:123
#define LOG_NOTICE
normal but significant condition
Definition n_log.h:104
#define LOG_NULL
no log output
Definition n_log.h:70
#define LOG_INFO
informational
Definition n_log.h:106
int truncated
1 when the edit limit was hit and the differing region was reported as one delete run followed by one...
Definition n_diff.h:108
size_t nb_deleted
number of N_DIFF_DELETE lines
Definition n_diff.h:103
size_t nb_inserted
number of N_DIFF_INSERT lines
Definition n_diff.h:105
size_t nb_common
number of N_DIFF_COMMON lines
Definition n_diff.h:101
LIST * lines
LIST of N_DIFF_LINE*, in output order.
Definition n_diff.h:99
void n_diff_result_free(N_DIFF_RESULT **result)
Free an edit script and every line it holds.
Definition n_diff.c:333
#define N_DIFF_NO_LINE
N_DIFF_LINE line_a / line_b value when the line has no counterpart there.
Definition n_diff.h:72
#define N_DIFF_DELETE
the line is only in the first text
Definition n_diff.h:67
#define N_DIFF_INSERT
the line is only in the second text
Definition n_diff.h:69
#define N_DIFF_CONTEXT_FULL
n_diff_to_unified() context asking for every common line, without hunk headers
Definition n_diff.h:75
N_DIFF_RESULT * n_diff_lines(const char *text_a, const char *text_b, size_t max_edits)
Compare two texts line by line.
Definition n_diff.c:252
N_STR * n_diff_to_unified(const N_DIFF_RESULT *result, size_t context)
Render an edit script as unified diff text.
Definition n_diff.c:391
one line of an edit script
Definition n_diff.h:85
edit script turning one text into another
Definition n_diff.h:97
size_t written
number of meaningful bytes in data, excluding the null terminator; the size including the null termin...
Definition n_str.h:68
char * data
the string
Definition n_str.h:63
#define free_nstr(__ptr)
free a N_STR structure and set the pointer to NULL
Definition n_str.h:203
#define nstrprintf_cat(__nstr_var, __format,...)
Macro to quickly allocate and sprintf and cat to a N_STR.
Definition n_str.h:121
N_STR * new_nstr(NSTRBYTE size)
create a new N_STR string
Definition n_str.c:209
A box including a string and his lenght.
Definition n_str.h:61
Common headers and low-level functions & define.
Line oriented text comparison (Myers shortest edit script).
Generic log system.
N_STR and string function declaration.