Merge pull request #23414 from keszybz/analyze-vercmp

systemd-analyze compare-versions
This commit is contained in:
Zbigniew Jędrzejewski-Szmek 2022-05-23 09:14:51 +02:00 committed by GitHub
commit 2f9b7186e3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 469 additions and 116 deletions

View file

@ -332,11 +332,11 @@ directory is found to be populated already in the root partition, the automatic
discovery _must not_ mount any discovered file system over it. Optionally, in
case of the root, `/usr/` and their Verity partitions instead of strictly
mounting the first suitable partition an OS might choose to mount the partition
whose label compares the highest according to `strverscmp()` or a similar
logic, in order to implement a simple partition-based A/B versioning
scheme. The precise rules are left for the implementation to decide, but when
in doubt earlier partitions (by their index) should always win over later
partitions if the label comparison is inconclusive.
whose label compares the highest according to `strverscmp()` or similar logic,
in order to implement a simple partition-based A/B versioning scheme. The
precise rules are left for the implementation to decide, but when in doubt
earlier partitions (by their index) should always win over later partitions if
the label comparison is inconclusive.
A *container* *manager* should automatically discover and mount the root,
`/usr/`, `/home/`, `/srv/`, `/var/`, `/var/tmp/` partitions inside a container

View file

@ -118,6 +118,14 @@
<arg choice="plain">cat-config</arg>
<arg choice="plain" rep="repeat"><replaceable>NAME</replaceable>|<replaceable>PATH</replaceable></arg>
</cmdsynopsis>
<cmdsynopsis>
<command>systemd-analyze</command>
<arg choice="opt" rep="repeat">OPTIONS</arg>
<arg choice="plain">compare-versions</arg>
<arg choice="plain"><replaceable>VERSION1</replaceable></arg>
<arg choice="opt"><replaceable>OP</replaceable></arg>
<arg choice="plain"><replaceable>VERSION2</replaceable></arg>
</cmdsynopsis>
<cmdsynopsis>
<command>systemd-analyze</command>
<arg choice="opt" rep="repeat">OPTIONS</arg>
@ -547,6 +555,52 @@ NAutoVTs=8
</example>
</refsect2>
<refsect2>
<title><command>systemd-analyze compare-versions
<replaceable>VERSION1</replaceable>
<optional><replaceable>OP</replaceable></optional>
<replaceable>VERSION2</replaceable></command></title>
<para>This command has two distinct modes of operation, depending on whether the operator
<replaceable>OP</replaceable> is specified.</para>
<para>In the first mode — when <replaceable>OP</replaceable> is not specified — it will compare the two
version strings and print either <literal><replaceable>VERSION1</replaceable> &lt;
<replaceable>VERSION2</replaceable></literal>, or <literal><replaceable>VERSION1</replaceable> ==
<replaceable>VERSION2</replaceable></literal>, or <literal><replaceable>VERSION1</replaceable> &gt;
<replaceable>VERSION2</replaceable></literal> as appropriate.</para>
<para>The exit status is <constant>0</constant> if the versions are equal, <constant>11</constant> if
the version of the right is smaller, and <constant>12</constant> if the version of the left is
smaller. (This matches the convention used by <command>rpmdev-vercmp</command>.)</para>
<para>In the second mode — when <replaceable>OP</replaceable> is specified — it will compare the two
version strings using the operation <replaceable>OP</replaceable> and return <constant>0</constant>
(success) if they condition is satisfied, and <constant>1</constant> (failure)
otherwise. <constant>OP</constant> may be <command>lt</command>, <command>le</command>,
<command>eq</command>, <command>ne</command>, <command>ge</command>, <command>gt</command>. In this
mode, no output is printed.
(This matches the convention used by
<citerefentry project='die-net'><refentrytitle>dpkg</refentrytitle><manvolnum>1</manvolnum></citerefentry>
<option>--compare-versions</option>.)</para>
<example>
<title>Compare versions of a package</title>
<programlisting>
$ systemd-analyze compare-versions systemd-250~rc1.fc36.aarch64 systemd-251.fc36.aarch64
systemd-250~rc1.fc36.aarch64 &lt; systemd-251.fc36.aarch64
$ echo $?
12
$ systemd-analyze compare-versions 1 lt 2; echo $?
0
$ systemd-analyze compare-versions 1 ge 2; echo $?
1
</programlisting>
</example>
</refsect2>
<refsect2>
<title><command>systemd-analyze verify <replaceable>FILE</replaceable>...</command></title>
@ -1197,8 +1251,12 @@ $ systemd-analyze verify /tmp/source:alias.service
<refsect1>
<title>Exit status</title>
<para>On success, 0 is returned, a non-zero failure code
otherwise.</para>
<para>For most commands, 0 is returned on success, and a non-zero failure code otherwise.</para>
<para>With the verb <command>compare-versions</command>, in the two-argument form,
<constant>12</constant>, <constant>0</constant>, <constant>11</constant> is returned if the second
version string is respectively larger, equal, or smaller to the first. In the three-argument form,
<constant>0</constant> or <constant>1</constant> if the condition is respectively true or false.</para>
</refsect1>
<xi:include href="common-variables.xml" />

View file

@ -2162,7 +2162,7 @@ meson.add_install_script(meson_make_symlink,
rootlibexecdir / 'systemd',
rootsbindir / 'init')
public_programs += executable(
exe = executable(
'systemd-analyze',
systemd_analyze_sources,
include_directories : core_includes,
@ -2172,6 +2172,13 @@ public_programs += executable(
libseccomp],
install_rpath : rootlibexecdir,
install : conf.get('ENABLE_ANALYZE'))
public_programs += exe
if want_tests != 'false'
test('test-compare-versions',
test_compare_versions_sh,
args : exe.full_path())
endif
executable(
'systemd-journald',

View file

@ -61,5 +61,9 @@ int verb_blame(int argc, char *argv[], void *userdata) {
pager_open(arg_pager_flags);
return table_print(table, NULL);
r = table_print(table, NULL);
if (r < 0)
return r;
return EXIT_SUCCESS;
}

View file

@ -123,7 +123,7 @@ static int test_calendar_one(usec_t n, const char *p) {
}
int verb_calendar(int argc, char *argv[], void *userdata) {
int ret = 0, r;
int r = 0;
usec_t n;
if (arg_base_time != USEC_INFINITY)
@ -132,13 +132,15 @@ int verb_calendar(int argc, char *argv[], void *userdata) {
n = now(CLOCK_REALTIME); /* We want to use the same "base" for all expressions */
STRV_FOREACH(p, strv_skip(argv, 1)) {
r = test_calendar_one(n, *p);
if (ret == 0 && r < 0)
ret = r;
int k;
if (*(p + 1))
k = test_calendar_one(n, *p);
if (r == 0 && k < 0)
r = k;
if (p[1])
putchar('\n');
}
return ret;
return r;
}

View file

@ -48,5 +48,9 @@ int verb_capabilities(int argc, char *argv[], void *userdata) {
pager_open(arg_pager_flags);
return table_print(table, NULL);
r = table_print(table, NULL);
if (r < 0)
return r;
return EXIT_SUCCESS;
}

View file

@ -42,5 +42,5 @@ int verb_cat_config(int argc, char *argv[], void *userdata) {
return r;
}
return 0;
return EXIT_SUCCESS;
}

View file

@ -0,0 +1,47 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <stdio.h>
#include "analyze-compare-versions.h"
#include "macro.h"
#include "string-util.h"
#include "strv.h"
int verb_compare_versions(int argc, char *argv[], void *userdata) {
int r;
assert(argc == 3 || argc == 4);
assert(argv);
if (argc == 3) {
r = strverscmp_improved(ASSERT_PTR(argv[1]), ASSERT_PTR(argv[2]));
printf("%s %s %s\n",
argv[1],
r < 0 ? "<" : r > 0 ? ">" : "==",
argv[2]);
/* This matches the exit convention used by rpmdev-vercmp.
* We don't use named values because 11 and 12 don't have names. */
return r < 0 ? 12 : r > 0 ? 11 : 0;
} else {
const char *op = ASSERT_PTR(argv[2]);
r = strverscmp_improved(ASSERT_PTR(argv[1]), ASSERT_PTR(argv[3]));
if (STR_IN_SET(op, "lt", "<"))
return r < 0 ? EXIT_SUCCESS : EXIT_FAILURE;
if (STR_IN_SET(op, "le", "<="))
return r <= 0 ? EXIT_SUCCESS : EXIT_FAILURE;
if (STR_IN_SET(op, "eq", "=="))
return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
if (STR_IN_SET(op, "ne", "!="))
return r != 0 ? EXIT_SUCCESS : EXIT_FAILURE;
if (STR_IN_SET(op, "ge", ">="))
return r >= 0 ? EXIT_SUCCESS : EXIT_FAILURE;
if (STR_IN_SET(op, "gt", ">"))
return r > 0 ? EXIT_SUCCESS : EXIT_FAILURE;
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Unknown operator \"%s\".", op);
}
}

View file

@ -0,0 +1,3 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
int verb_compare_versions(int argc, char *argv[], void *userdata);

View file

@ -137,5 +137,11 @@ static int verify_conditions(char **lines, LookupScope scope, const char *unit,
}
int verb_condition(int argc, char *argv[], void *userdata) {
return verify_conditions(strv_skip(argv, 1), arg_scope, arg_unit, arg_root);
int r;
r = verify_conditions(strv_skip(argv, 1), arg_scope, arg_unit, arg_root);
if (r < 0)
return r;
return EXIT_SUCCESS;
}

View file

@ -1,15 +1,19 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "analyze.h"
#include "analyze-critical-chain.h"
#include "analyze-time-data.h"
#include "strv.h"
#include "analyze.h"
#include "bus-error.h"
#include "copy.h"
#include "path-util.h"
#include "terminal-util.h"
#include "sort-util.h"
#include "special.h"
#include "bus-error.h"
#include "static-destruct.h"
#include "strv.h"
#include "terminal-util.h"
static Hashmap *unit_times_hashmap = NULL;
STATIC_DESTRUCTOR_REGISTER(unit_times_hashmap, hashmap_freep);
static int list_dependencies_print(
const char *name,
@ -54,8 +58,6 @@ static int list_dependencies_get_dependencies(sd_bus *bus, const char *name, cha
return bus_get_unit_property_strv(bus, path, "After", deps);
}
static Hashmap *unit_times_hashmap;
static int list_dependencies_compare(char *const *a, char *const *b) {
usec_t usa = 0, usb = 0;
UnitTimes *times;
@ -197,7 +199,6 @@ static int list_dependencies(sd_bus *bus, const char *name) {
int verb_critical_chain(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
_cleanup_(unit_times_free_arrayp) UnitTimes *times = NULL;
Hashmap *h;
int n, r;
r = acquire_bus(&bus, NULL);
@ -208,16 +209,11 @@ int verb_critical_chain(int argc, char *argv[], void *userdata) {
if (n <= 0)
return n;
h = hashmap_new(&string_hash_ops);
if (!h)
return log_oom();
for (UnitTimes *u = times; u->has_data; u++) {
r = hashmap_put(h, u->name, u);
r = hashmap_ensure_put(&unit_times_hashmap, &string_hash_ops, u->name, u);
if (r < 0)
return log_error_errno(r, "Failed to add entry to hashmap: %m");
}
unit_times_hashmap = h;
pager_open(arg_pager_flags);
@ -230,6 +226,5 @@ int verb_critical_chain(int argc, char *argv[], void *userdata) {
else
list_dependencies(bus, SPECIAL_DEFAULT_TARGET);
h = hashmap_free(h);
return 0;
return EXIT_SUCCESS;
}

View file

@ -60,5 +60,9 @@ int verb_dump(int argc, char *argv[], void *userdata) {
return bus_log_parse_error(r);
fflush(stdout);
return copy_bytes(fd, STDOUT_FILENO, UINT64_MAX, 0);
r = copy_bytes(fd, STDOUT_FILENO, UINT64_MAX, 0);
if (r < 0)
return r;
return EXIT_SUCCESS;
}

View file

@ -48,5 +48,9 @@ int verb_exit_status(int argc, char *argv[], void *userdata) {
pager_open(arg_pager_flags);
return table_print(table, NULL);
r = table_print(table, NULL);
if (r < 0)
return r;
return EXIT_SUCCESS;
}

View file

@ -221,5 +221,5 @@ int verb_filesystems(int argc, char *argv[], void *userdata) {
first = false;
}
return 0;
return EXIT_SUCCESS;
}

View file

@ -14,5 +14,9 @@ int verb_log_control(int argc, char *argv[], void *userdata) {
if (r < 0)
return bus_log_connect_error(r, arg_transport);
return verb_log_control_common(bus, "org.freedesktop.systemd1", argv[0], argc == 2 ? argv[1] : NULL);
r = verb_log_control_common(bus, "org.freedesktop.systemd1", argv[0], argc == 2 ? argv[1] : NULL);
if (r < 0)
return r;
return EXIT_SUCCESS;
}

View file

@ -391,5 +391,5 @@ int verb_plot(int argc, char *argv[], void *userdata) {
svg("</svg>\n");
return 0;
return EXIT_SUCCESS;
}

View file

@ -37,5 +37,5 @@ int verb_service_watchdogs(int argc, char *argv[], void *userdata) {
return log_error_errno(r, "Failed to set service-watchdog state: %s", bus_error_message(&error, r));
}
return 0;
return EXIT_SUCCESS;
}

View file

@ -176,7 +176,7 @@ int verb_syscall_filters(int argc, char *argv[], void *userdata) {
first = false;
}
return 0;
return EXIT_SUCCESS;
}
#else

View file

@ -1,6 +1,8 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <sd-bus.h>
#include "time-util.h"
typedef struct BootTimes {

View file

@ -18,5 +18,5 @@ int verb_time(int argc, char *argv[], void *userdata) {
return r;
puts(buf);
return 0;
return EXIT_SUCCESS;
}

View file

@ -79,16 +79,18 @@ static int test_timestamp_one(const char *p) {
}
int verb_timestamp(int argc, char *argv[], void *userdata) {
int ret = 0, r;
int r = 0;
STRV_FOREACH(p, strv_skip(argv, 1)) {
r = test_timestamp_one(*p);
if (ret == 0 && r < 0)
ret = r;
int k;
if (*(p + 1))
k = test_timestamp_one(*p);
if (r == 0 && k < 0)
r = k;
if (p[1])
putchar('\n');
}
return ret;
return r;
}

View file

@ -46,5 +46,5 @@ int verb_unit_files(int argc, char *argv[], void *userdata) {
printf("aliases: %s ← %s\n", k, j);
}
return 0;
return EXIT_SUCCESS;
}

View file

@ -16,5 +16,5 @@ int verb_unit_paths(int argc, char *argv[], void *userdata) {
STRV_FOREACH(p, paths.search_path)
puts(*p);
return 0;
return EXIT_SUCCESS;
}

View file

@ -35,6 +35,7 @@
#include "analyze-timestamp.h"
#include "analyze-unit-files.h"
#include "analyze-unit-paths.h"
#include "analyze-compare-versions.h"
#include "analyze-verify.h"
#include "bus-error.h"
#include "bus-locator.h"
@ -199,6 +200,8 @@ static int help(int argc, char *argv[], void *userdata) {
" syscall-filter [NAME...] List syscalls in seccomp filters\n"
" filesystems [NAME...] List known filesystems\n"
" condition CONDITION... Evaluate conditions and asserts\n"
" compare-versions VERSION1 [OP] VERSION2\n"
" Compare two version strings\n"
" verify FILE... Check unit files for correctness\n"
" calendar SPEC... Validate repetitive calendar time\n"
" events\n"
@ -564,6 +567,7 @@ static int run(int argc, char *argv[]) {
{ "capability", VERB_ANY, VERB_ANY, 0, verb_capabilities },
{ "filesystems", VERB_ANY, VERB_ANY, 0, verb_filesystems },
{ "condition", VERB_ANY, VERB_ANY, 0, verb_condition },
{ "compare-versions", 3, 4, 0, verb_compare_versions },
{ "verify", 2, VERB_ANY, 0, verb_verify },
{ "calendar", 2, VERB_ANY, 0, verb_calendar },
{ "timestamp", 2, VERB_ANY, 0, verb_timestamp },
@ -607,4 +611,4 @@ static int run(int argc, char *argv[]) {
return dispatch_verb(argc, argv, verbs, NULL);
}
DEFINE_MAIN_FUNCTION(run);
DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);

View file

@ -9,6 +9,8 @@ systemd_analyze_sources = files(
'analyze-capability.h',
'analyze-cat-config.c',
'analyze-cat-config.h',
'analyze-compare-versions.c',
'analyze-compare-versions.h',
'analyze-condition.c',
'analyze-condition.h',
'analyze-critical-chain.c',

View file

@ -2,7 +2,8 @@
#pragma once
#ifndef SD_BOOT
#include <assert.h>
# include <assert.h>
# include <stddef.h>
#endif
#include <limits.h>

View file

@ -1,10 +1,10 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#ifndef SD_BOOT
#include <ctype.h>
#include "macro.h"
# include <ctype.h>
#endif
#include "macro-fundamental.h"
#include "string-util-fundamental.h"
sd_char *startswith(const sd_char *s, const sd_char *prefix) {
@ -77,36 +77,37 @@ sd_char* endswith_no_case(const sd_char *s, const sd_char *postfix) {
return (sd_char*) s + sl - pl;
}
#ifdef SD_BOOT
static sd_bool isdigit(sd_char a) {
static sd_bool is_digit(sd_char a) {
/* Locale-independent version of isdigit(). */
return a >= '0' && a <= '9';
}
#endif
static sd_bool is_alpha(sd_char a) {
/* Locale independent version of isalpha(). */
/* Locale-independent version of isalpha(). */
return (a >= 'a' && a <= 'z') || (a >= 'A' && a <= 'Z');
}
static sd_bool is_valid_version_char(sd_char a) {
return isdigit(a) || is_alpha(a) || IN_SET(a, '~', '-', '^', '.');
return is_digit(a) || is_alpha(a) || IN_SET(a, '~', '-', '^', '.');
}
sd_int strverscmp_improved(const sd_char *a, const sd_char *b) {
/* This is based on RPM's rpmvercmp(). But this explicitly handles '-' and '.', as we usually
* want to directly compare strings which contain both version and release; e.g.
* '247.2-3.1.fc33.x86_64' or '5.11.0-0.rc5.20210128git76c057c84d28.137.fc34'.
* Unlike rpmvercmp(), this distiguishes e.g. 123a and 123.a, and 123a is newer.
/* This function is similar to strverscmp(3), but it treats '-' and '.' as separators.
*
* This splits the input strings into segments. Each segment is numeric or alpha, and may be
* The logic is based on rpm's rpmvercmp(), but unlike rpmvercmp(), it distiguishes e.g.
* '123a' and '123.a', with '123a' being newer.
*
* It allows direct comparison of strings which contain both a version and a release; e.g.
* '247.2-3.1.fc33.x86_64' or '5.11.0-0.rc5.20210128git76c057c84d28.137.fc34'.
*
* The input string is split into segments. Each segment is numeric or alphabetic, and may be
* prefixed with the following:
* '~' : used for pre-releases, a segment prefixed with this is the oldest,
* '-' : used for the separator between version and release,
* '^' : used for patched releases, a segment with this is newer than one with '-'.
* '.' : used for point releases.
* Note, no prefix segment is the newest. All non-supported characters are dropped, and
* handled as a separator of segments, e.g., 123_a is equivalent to 123a.
* Note that no prefix segment is the newest. All non-supported characters are dropped, and
* handled as a separator of segments, e.g., '123_a' is equivalent to '123a'.
*
* By using this, version strings can be sorted like following:
* (older) 122.1
@ -124,7 +125,7 @@ sd_int strverscmp_improved(const sd_char *a, const sd_char *b) {
*/
if (isempty(a) || isempty(b))
return strcmp_ptr(a, b);
return CMP(strcmp_ptr(a, b), 0);
for (;;) {
const sd_char *aa, *bb;
@ -185,7 +186,7 @@ sd_int strverscmp_improved(const sd_char *a, const sd_char *b) {
b++;
}
if (isdigit(*a) || isdigit(*b)) {
if (is_digit(*a) || is_digit(*b)) {
/* Skip leading '0', to make 00123 equivalent to 123. */
while (*a == '0')
a++;
@ -194,9 +195,9 @@ sd_int strverscmp_improved(const sd_char *a, const sd_char *b) {
/* Find the leading numeric segments. One may be an empty string. So,
* numeric segments are always newer than alpha segments. */
for (aa = a; isdigit(*aa); aa++)
for (aa = a; is_digit(*aa); aa++)
;
for (bb = b; isdigit(*bb); bb++)
for (bb = b; is_digit(*bb); bb++)
;
/* To compare numeric segments without parsing their values, first compare the
@ -206,7 +207,7 @@ sd_int strverscmp_improved(const sd_char *a, const sd_char *b) {
return r;
/* Then, compare them as strings. */
r = strncmp(a, b, aa - a);
r = CMP(strncmp(a, b, aa - a), 0);
if (r != 0)
return r;
} else {
@ -217,7 +218,7 @@ sd_int strverscmp_improved(const sd_char *a, const sd_char *b) {
;
/* Note that the segments are usually not NUL-terminated. */
r = strncmp(a, b, MIN(aa - a, bb - b));
r = CMP(strncmp(a, b, MIN(aa - a, bb - b)), 0);
if (r != 0)
return r;

View file

@ -30,27 +30,22 @@ int verb_log_control_common(sd_bus *bus, const char *destination, const char *ve
r = bus_set_property(bus, &bloc,
level ? "LogLevel" : "LogTarget",
&error, "s", value);
if (r >= 0)
return 0;
log_error_errno(r, "Failed to set log %s of %s to %s: %s",
level ? "level" : "target",
bloc.destination, value, bus_error_message(&error, r));
if (r < 0)
return log_error_errno(r, "Failed to set log %s of %s to %s: %s",
level ? "level" : "target",
bloc.destination, value, bus_error_message(&error, r));
} else {
_cleanup_free_ char *t = NULL;
r = bus_get_property_string(bus, &bloc,
level ? "LogLevel" : "LogTarget",
&error, &t);
if (r >= 0) {
puts(t);
return 0;
}
log_error_errno(r, "Failed to get log %s of %s: %s",
level ? "level" : "target",
bloc.destination, bus_error_message(&error, r));
if (r < 0)
return log_error_errno(r, "Failed to get log %s of %s: %s",
level ? "level" : "target",
bloc.destination, bus_error_message(&error, r));
puts(t);
}
return r;
return 0;
}

View file

@ -1,5 +1,7 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <ctype.h>
#include "alloc-util.h"
#include "locale-util.h"
#include "macro.h"
@ -828,13 +830,24 @@ TEST(string_contains_word) {
assert_se(!string_contains_word("a:b:cc", ":#", ":cc"));
}
static void test_strverscmp_improved_one(const char *newer, const char *older) {
log_info("/* %s(%s, %s) */", __func__, strnull(newer), strnull(older));
static void test_strverscmp_improved_one(const char* a, const char *b, int expected) {
int r = strverscmp_improved(a, b);
log_info("'%s' %s '%s'%s",
strnull(a),
r > 0 ? ">" : r < 0 ? "<" : "==",
strnull(b),
r == expected ? "" : " !!!!!!!!!!!!!");
assert_se(r == expected);
}
static void test_strverscmp_improved_newer(const char *older, const char *newer) {
test_strverscmp_improved_one(older, newer, -1);
assert_se(strverscmp_improved(newer, newer) == 0);
assert_se(strverscmp_improved(newer, older) > 0);
assert_se(strverscmp_improved(older, newer) < 0);
assert_se(strverscmp_improved(older, older) == 0);
assert_se(strverscmp_improved(older, newer) < 0);
assert_se(strverscmp_improved(newer, older) > 0);
assert_se(strverscmp_improved(newer, newer) == 0);
}
TEST(strverscmp_improved) {
@ -868,41 +881,194 @@ TEST(strverscmp_improved) {
STRV_FOREACH(p, versions)
STRV_FOREACH(q, p + 1)
test_strverscmp_improved_one(*q, *p);
test_strverscmp_improved_newer(*p, *q);
test_strverscmp_improved_one("123.45-67.89", "123.45-67.88");
test_strverscmp_improved_one("123.45-67.89a", "123.45-67.89");
test_strverscmp_improved_one("123.45-67.89", "123.45-67.ab");
test_strverscmp_improved_one("123.45-67.89", "123.45-67.9");
test_strverscmp_improved_one("123.45-67.89", "123.45-67");
test_strverscmp_improved_one("123.45-67.89", "123.45-66.89");
test_strverscmp_improved_one("123.45-67.89", "123.45-9.99");
test_strverscmp_improved_one("123.45-67.89", "123.42-99.99");
test_strverscmp_improved_one("123.45-67.89", "123-99.99");
test_strverscmp_improved_newer("123.45-67.88", "123.45-67.89");
test_strverscmp_improved_newer("123.45-67.89", "123.45-67.89a");
test_strverscmp_improved_newer("123.45-67.ab", "123.45-67.89");
test_strverscmp_improved_newer("123.45-67.9", "123.45-67.89");
test_strverscmp_improved_newer("123.45-67", "123.45-67.89");
test_strverscmp_improved_newer("123.45-66.89", "123.45-67.89");
test_strverscmp_improved_newer("123.45-9.99", "123.45-67.89");
test_strverscmp_improved_newer("123.42-99.99", "123.45-67.89");
test_strverscmp_improved_newer("123-99.99", "123.45-67.89");
/* '~' : pre-releases */
test_strverscmp_improved_one("123.45-67.89", "123~rc1-99.99");
test_strverscmp_improved_one("123-45.67.89", "123~rc1-99.99");
test_strverscmp_improved_one("123~rc2-67.89", "123~rc1-99.99");
test_strverscmp_improved_one("123^aa2-67.89", "123~rc1-99.99");
test_strverscmp_improved_one("123aa2-67.89", "123~rc1-99.99");
test_strverscmp_improved_newer("123~rc1-99.99", "123.45-67.89");
test_strverscmp_improved_newer("123~rc1-99.99", "123-45.67.89");
test_strverscmp_improved_newer("123~rc1-99.99", "123~rc2-67.89");
test_strverscmp_improved_newer("123~rc1-99.99", "123^aa2-67.89");
test_strverscmp_improved_newer("123~rc1-99.99", "123aa2-67.89");
/* '-' : separator between version and release. */
test_strverscmp_improved_one("123.45-67.89", "123-99.99");
test_strverscmp_improved_one("123^aa2-67.89", "123-99.99");
test_strverscmp_improved_one("123aa2-67.89", "123-99.99");
test_strverscmp_improved_newer("123-99.99", "123.45-67.89");
test_strverscmp_improved_newer("123-99.99", "123^aa2-67.89");
test_strverscmp_improved_newer("123-99.99", "123aa2-67.89");
/* '^' : patch releases */
test_strverscmp_improved_one("123.45-67.89", "123^45-67.89");
test_strverscmp_improved_one("123^aa2-67.89", "123^aa1-99.99");
test_strverscmp_improved_one("123aa2-67.89", "123^aa2-67.89");
test_strverscmp_improved_newer("123^45-67.89", "123.45-67.89");
test_strverscmp_improved_newer("123^aa1-99.99", "123^aa2-67.89");
test_strverscmp_improved_newer("123^aa2-67.89", "123aa2-67.89");
/* '.' : point release */
test_strverscmp_improved_one("123aa2-67.89", "123.aa2-67.89");
test_strverscmp_improved_one("123.ab2-67.89", "123.aa2-67.89");
test_strverscmp_improved_newer("123.aa2-67.89", "123aa2-67.89");
test_strverscmp_improved_newer("123.aa2-67.89", "123.ab2-67.89");
/* invalid characters */
assert_se(strverscmp_improved("123_aa2-67.89", "123aa+2-67.89") == 0);
/* non-ASCII digits */
(void) setlocale(LC_NUMERIC, "ar_YE.utf8");
assert_se(strverscmp_improved("1٠١٢٣٤٥٦٧٨٩", "1") == 0);
(void) setlocale(LC_NUMERIC, "th_TH.utf8");
assert_se(strverscmp_improved("1๑๒๓๔๕๖๗๘๙", "1") == 0);
}
#define RPMVERCMP(a, b, c) \
test_strverscmp_improved_one(STRINGIFY(a), STRINGIFY(b), (c))
TEST(strverscmp_improved_rpm) {
/* Tests copied from rmp's rpmio test suite, under the LGPL license:
* https://github.com/rpm-software-management/rpm/blob/master/tests/rpmvercmp.at.
* The original form is retained for easy comparisons and updates.
*/
RPMVERCMP(1.0, 1.0, 0);
RPMVERCMP(1.0, 2.0, -1);
RPMVERCMP(2.0, 1.0, 1);
RPMVERCMP(2.0.1, 2.0.1, 0);
RPMVERCMP(2.0, 2.0.1, -1);
RPMVERCMP(2.0.1, 2.0, 1);
RPMVERCMP(2.0.1a, 2.0.1a, 0);
RPMVERCMP(2.0.1a, 2.0.1, 1);
RPMVERCMP(2.0.1, 2.0.1a, -1);
RPMVERCMP(5.5p1, 5.5p1, 0);
RPMVERCMP(5.5p1, 5.5p2, -1);
RPMVERCMP(5.5p2, 5.5p1, 1);
RPMVERCMP(5.5p10, 5.5p10, 0);
RPMVERCMP(5.5p1, 5.5p10, -1);
RPMVERCMP(5.5p10, 5.5p1, 1);
RPMVERCMP(10xyz, 10.1xyz, 1); /* Note: this is reversed from rpm's vercmp */
RPMVERCMP(10.1xyz, 10xyz, -1); /* Note: this is reversed from rpm's vercmp */
RPMVERCMP(xyz10, xyz10, 0);
RPMVERCMP(xyz10, xyz10.1, -1);
RPMVERCMP(xyz10.1, xyz10, 1);
RPMVERCMP(xyz.4, xyz.4, 0);
RPMVERCMP(xyz.4, 8, -1);
RPMVERCMP(8, xyz.4, 1);
RPMVERCMP(xyz.4, 2, -1);
RPMVERCMP(2, xyz.4, 1);
RPMVERCMP(5.5p2, 5.6p1, -1);
RPMVERCMP(5.6p1, 5.5p2, 1);
RPMVERCMP(5.6p1, 6.5p1, -1);
RPMVERCMP(6.5p1, 5.6p1, 1);
RPMVERCMP(6.0.rc1, 6.0, 1);
RPMVERCMP(6.0, 6.0.rc1, -1);
RPMVERCMP(10b2, 10a1, 1);
RPMVERCMP(10a2, 10b2, -1);
RPMVERCMP(1.0aa, 1.0aa, 0);
RPMVERCMP(1.0a, 1.0aa, -1);
RPMVERCMP(1.0aa, 1.0a, 1);
RPMVERCMP(10.0001, 10.0001, 0);
RPMVERCMP(10.0001, 10.1, 0);
RPMVERCMP(10.1, 10.0001, 0);
RPMVERCMP(10.0001, 10.0039, -1);
RPMVERCMP(10.0039, 10.0001, 1);
RPMVERCMP(4.999.9, 5.0, -1);
RPMVERCMP(5.0, 4.999.9, 1);
RPMVERCMP(20101121, 20101121, 0);
RPMVERCMP(20101121, 20101122, -1);
RPMVERCMP(20101122, 20101121, 1);
RPMVERCMP(2_0, 2_0, 0);
RPMVERCMP(2.0, 2_0, -1); /* Note: in rpm those compare equal */
RPMVERCMP(2_0, 2.0, 1); /* Note: in rpm those compare equal */
/* RhBug:178798 case */
RPMVERCMP(a, a, 0);
RPMVERCMP(a+, a+, 0);
RPMVERCMP(a+, a_, 0);
RPMVERCMP(a_, a+, 0);
RPMVERCMP(+a, +a, 0);
RPMVERCMP(+a, _a, 0);
RPMVERCMP(_a, +a, 0);
RPMVERCMP(+_, +_, 0);
RPMVERCMP(_+, +_, 0);
RPMVERCMP(_+, _+, 0);
RPMVERCMP(+, _, 0);
RPMVERCMP(_, +, 0);
/* Basic testcases for tilde sorting */
RPMVERCMP(1.0~rc1, 1.0~rc1, 0);
RPMVERCMP(1.0~rc1, 1.0, -1);
RPMVERCMP(1.0, 1.0~rc1, 1);
RPMVERCMP(1.0~rc1, 1.0~rc2, -1);
RPMVERCMP(1.0~rc2, 1.0~rc1, 1);
RPMVERCMP(1.0~rc1~git123, 1.0~rc1~git123, 0);
RPMVERCMP(1.0~rc1~git123, 1.0~rc1, -1);
RPMVERCMP(1.0~rc1, 1.0~rc1~git123, 1);
/* Basic testcases for caret sorting */
RPMVERCMP(1.0^, 1.0^, 0);
RPMVERCMP(1.0^, 1.0, 1);
RPMVERCMP(1.0, 1.0^, -1);
RPMVERCMP(1.0^git1, 1.0^git1, 0);
RPMVERCMP(1.0^git1, 1.0, 1);
RPMVERCMP(1.0, 1.0^git1, -1);
RPMVERCMP(1.0^git1, 1.0^git2, -1);
RPMVERCMP(1.0^git2, 1.0^git1, 1);
RPMVERCMP(1.0^git1, 1.01, -1);
RPMVERCMP(1.01, 1.0^git1, 1);
RPMVERCMP(1.0^20160101, 1.0^20160101, 0);
RPMVERCMP(1.0^20160101, 1.0.1, -1);
RPMVERCMP(1.0.1, 1.0^20160101, 1);
RPMVERCMP(1.0^20160101^git1, 1.0^20160101^git1, 0);
RPMVERCMP(1.0^20160102, 1.0^20160101^git1, 1);
RPMVERCMP(1.0^20160101^git1, 1.0^20160102, -1);
/* Basic testcases for tilde and caret sorting */
RPMVERCMP(1.0~rc1^git1, 1.0~rc1^git1, 0);
RPMVERCMP(1.0~rc1^git1, 1.0~rc1, 1);
RPMVERCMP(1.0~rc1, 1.0~rc1^git1, -1);
RPMVERCMP(1.0^git1~pre, 1.0^git1~pre, 0);
RPMVERCMP(1.0^git1, 1.0^git1~pre, 1);
RPMVERCMP(1.0^git1~pre, 1.0^git1, -1);
/* These are included here to document current, arguably buggy behaviors
* for reference purposes and for easy checking against unintended
* behavior changes. */
log_info("/* RPM version comparison oddities */");
/* RhBug:811992 case */
RPMVERCMP(1b.fc17, 1b.fc17, 0);
RPMVERCMP(1b.fc17, 1.fc17, 1); /* Note: this is reversed from rpm's vercmp, WAT! */
RPMVERCMP(1.fc17, 1b.fc17, -1);
RPMVERCMP(1g.fc17, 1g.fc17, 0);
RPMVERCMP(1g.fc17, 1.fc17, 1);
RPMVERCMP(1.fc17, 1g.fc17, -1);
/* Non-ascii characters are considered equal so these are all the same, eh… */
RPMVERCMP(1.1.α, 1.1.α, 0);
RPMVERCMP(1.1.α, 1.1.β, 0);
RPMVERCMP(1.1.β, 1.1.α, 0);
RPMVERCMP(1.1.αα, 1.1.α, 0);
RPMVERCMP(1.1.α, 1.1.ββ, 0);
RPMVERCMP(1.1.ββ, 1.1.αα, 0);
}
TEST(strextendf) {

View file

@ -108,6 +108,14 @@ endif
############################################################
test_compare_versions_sh = files('test-compare-versions.sh')
if install_tests
install_data(test_compare_versions_sh,
install_dir : testsdir)
endif
############################################################
rule_syntax_check_py = find_program('rule-syntax-check.py')
if want_tests != 'false'
test('rule-syntax-check',

34
test/test-compare-versions.sh Executable file
View file

@ -0,0 +1,34 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: LGPL-2.1-or-later
set -e
ANALYZE="${1:-systemd-analyze}"
$ANALYZE compare-versions 1 lt 2
$ANALYZE compare-versions 1 '<' 2
$ANALYZE compare-versions 1 le 2
$ANALYZE compare-versions 1 '<=' 2
$ANALYZE compare-versions 1 ne 2
$ANALYZE compare-versions 1 '!=' 2
( ! $ANALYZE compare-versions 1 ge 2 )
( ! $ANALYZE compare-versions 1 '>=' 2 )
( ! $ANALYZE compare-versions 1 eq 2 )
( ! $ANALYZE compare-versions 1 '==' 2 )
( ! $ANALYZE compare-versions 1 gt 2 )
( ! $ANALYZE compare-versions 1 '>' 2 )
$ANALYZE compare-versions 1 2 | grep ' < '
$ANALYZE compare-versions 2 2 | grep ' == '
$ANALYZE compare-versions 2 1 | grep ' > '
set +e
$ANALYZE compare-versions 1 2; ret1=$?
$ANALYZE compare-versions 2 2; ret2=$?
$ANALYZE compare-versions 2 1; ret3=$?
set -e
test $ret1 == 12
test $ret2 == 0
test $ret3 == 11