Compare commits

...

10 commits

Author SHA1 Message Date
Nathan Fisher
89e9465062 Add test runner 2024-04-17 03:13:55 -04:00
3bef2a97c0 Bug fixes 2024-03-20 21:55:40 -04:00
21d15e0d0a Begin formatting doxygen style documentation comments 2024-02-13 01:38:07 -05:00
Nathan Fisher
2de5098f34 Simplification in parsePreRelease 2024-02-12 19:09:13 -05:00
Nathan Fisher
e4fe1c5740 Add parseGitRev function 2024-02-12 19:05:52 -05:00
0606189269 Make parsePreRelease copy the original string into a fixed width
buffer so it avoids modifying the original string via `strntok` or
similar
2024-02-12 12:20:29 -05:00
9451f29440 Progress on parse functions. TODO - parseGitRev 2024-02-12 10:56:26 -05:00
fd17180496 Add two tests 2024-02-12 00:15:00 -05:00
d67b34519f Simplify data structures 2024-02-10 23:18:06 -05:00
Nathan Fisher
b035e0018a Fixes and formatting 2024-02-10 19:11:35 -05:00
10 changed files with 3174 additions and 172 deletions

1
.gitignore vendored
View file

@ -1,3 +1,4 @@
doc/
test/output/ test/output/
test/* test/*
!test/*.c !test/*.c

2822
Doxyfile Normal file

File diff suppressed because it is too large Load diff

View file

@ -63,6 +63,9 @@ libsemver.a: $(objs)
libsemver.so: $(objs) libsemver.so: $(objs)
$(CC) -shared -o $@ $? $(LIBS) $(CC) -shared -o $@ $? $(LIBS)
docs: Doxyfile $(hdrs)
doxygen
install: install_include install_shared install_static install: install_include install_shared install_static
install_include: include/semver.h install_include: include/semver.h
@ -80,12 +83,11 @@ install_shared: libsemver.so
test: libsemver.a test: libsemver.a
$(MAKE) -C test $(MAKE) -C test
testclean: testclean: clean
$(MAKE) -C test clean $(MAKE) -C test clean
clean: clean:
rm -rf *.a *.so *.o rm -rf *.a *.so *.o doc
$(MAKE) -C test clean
.PHONY: all shared static clean install install_include \ .PHONY: all shared static clean install install_include \
install_static install_shared testclean test install_static install_shared testclean test

View file

@ -0,0 +1,7 @@
PREFIX ?= /usr/local
bindir = $(DESTDIR)$(PREFIX)/bin
includedir = $(DESTDIR)$(PREFIX)/include
libdir = $(DESTDIR)$(PREFIX)/lib
sharedir = $(DESTDIR)$(PREFIX)/share
mandir = $(sharedir)/man
docdir = $(sharedir)/doc/libsemver

View file

@ -1,94 +1,138 @@
/** \file semver.h
* \brief A Version manipulation library
*/
#include "epoch.h" #include "epoch.h"
#include <stdint.h> #include <stdint.h>
#define u128 __uint128_t #define u128 __uint128_t ///< A 128 bit unsigned integer
#define U12_MAX 4096 ///< The maximum size for semver fields
typedef struct {
uint16_t major;
} __Simple;
typedef struct {
uint16_t major;
uint16_t minor;
} __Rapid;
typedef struct {
uint16_t major;
uint16_t minor;
uint16_t patch;
} __SemVer;
typedef struct {
uint16_t major;
uint16_t minor;
uint16_t patch;
uint16_t build;
} __Extended;
/** An enumeration representing the type of versioning being used */
typedef enum { typedef enum {
Simple, Simple, /**< A single numeric identifier, ie `Firefox 102` */
Rapid, Rapid, /**< A two digit versioning scheme */
SemVer, SemVer, /**< Ordinary `SemVer` versioning, ie `major.minor.patch` */
Extended, Extended, /**< An extended semver type with an extra `build` field */
} VersionKindTag; } VersionKindTag;
/** Represents a Git version control revision as a pre-release */
typedef struct { typedef struct {
char hash[7]; char hash[7]; /**< The short form hash of a Git revision */
DateTime dt; DateTime dt; /**< The date and time of the Git revision */
} GitRevision; } GitRevision;
/** An enumeration representing the type of pre-release, if any */
typedef enum { typedef enum {
Alpha, Alpha,
Beta, Beta,
ReleaseCandidate, ReleaseCandidate, /**< An rc prerelease is the last stage in testing prior
GitRev, to a final releas */
PRNone, GitRev, /**< A Git version control revision used as a pre-release
`preview` */
PRNone, /**< Not a pre-release, or in other words a final release
version */
} PreReleaseTag; } PreReleaseTag;
typedef enum { typedef enum {
any, any,
arm, arm,
arm64, arm64,
loongson, loongson,
mips32, mips32,
mips64, mips64,
powerepc, powerepc,
powerpc64, powerpc64,
riscv64, riscv64,
s390x, s390x,
sparc, sparc,
sparc64, sparc64,
x86, x86,
x86_64, x86_64,
} Arch; } Arch;
/** A tagged union type storing a pre-release in one of several forms */
typedef struct { typedef struct {
VersionKindTag vk_tag; PreReleaseTag tag; /**< The tag field specifies the type of pre-release */
union { union {
__Simple simple; uint16_t alpha; /**< The numerical component of an `Alpha` pre-release */
__Rapid rapid; uint16_t beta; /**< The numerical component of a `Beta` pre-release */
__SemVer semver; uint16_t rc; /**< The numerical component of a release candidate */
__Extended extended; GitRevision git; /**< A Git version control revision as a pre-release */
} vk_data; };
PreReleaseTag pr_tag; } PreRelease;
union {
uint16_t alpha; /** Stores all relevant information to represent several different styles of versioning */
uint16_t beta; typedef struct {
uint16_t rc; VersionKindTag vk_tag; /**< Specifies the type of versioning scheme being usef */
GitRevision *git; union {
} pr_data; struct {
Arch arch; uint16_t major;
} simple; /**< A single number versioning scheme */
struct {
uint16_t major; /**< The "series" number, representing significant
api changes */
uint16_t minor; /**< The api number. Any breaking change must bump
this number. */
} rapid; /**< A two number versioning scheme */
struct {
uint16_t major; /**< The "series" number, representing significant
api changes */
uint16_t minor; /**< The api number. Any breaking change must bump
this number. */
uint16_t patch; /**< Patches and bugfixes that do not break api */
} semver; /**< The traditional three digit semver scheme */
struct {
uint16_t major;
uint16_t minor;
uint16_t patch;
uint16_t build;
} extended; /**< An extended semver-like scheme with an extra
`build` identifier */
};
PreRelease pr; /**< The pre-release identifier, if any */
Arch arch; /**< The processor architecture this package is
compiled for */
} Version; } Version;
/** Used to order Version structures */
typedef enum { typedef enum {
CompGreater, CompGreater,
CompEqual, CompEqual,
CompLess, CompLess,
CompNone, CompNone,
} Comparison; } Comparison;
u128 u128FromVersion(Version *self); /**
Comparison compareVersion(Version *self, Version *other); * Creates a 128 bit unsigned integer from the data structure in such a way that
int parseVersion(Version *self, const char *s); * versions can be compared and ordered. The `major` number has it's bits shifted
char *versionToString(Version *self); * by the furthest amount, followed by the `minor` (if it exists) and so on. The
* pre-release tag enum members each get their own bit, ordered in such a way that
* `PRNone` is considered greater than a release candidate and so on. The least
* significant 64 bits are taken up by the Unix timestamp of a git revision, if
* this is a Git pre-release
* ### Return values
* Returns the computed numerical representation
* \param self A `Version` struct to be converted
*/
u128 u128FromVersion(Version *self);
/**
* Compares `self` with `other` for purposes of ordering
* ### Return values
* Returns a `Comparison` enumeration value.
* \param self The first Version to be compared
* \param other The Version against which `self` will be compared
*/
Comparison compareVersion (Version *self, Version *other);
/**
* Parses a Version from a string
* ### Return values
* Returns 0 on success
* \param self A pointer to a memory region to store this Version structure
* \param s The string to be parsed
*/
int parseVersion (Version *self, const char *s);
char *versionToString(Version *self);

158
semver.c
View file

@ -13,46 +13,46 @@ u128 u128FromVersion(Version *self) {
switch (self->vk_tag) { switch (self->vk_tag) {
case Simple: case Simple:
out |= ((u128)self->vk_data.simple.major << (64 + 52)); out |= ((u128)self->simple.major << (64 + 52));
break; break;
case Rapid: case Rapid:
major = (uint64_t)self->vk_data.rapid.major << 52; major = (uint64_t)self->rapid.major << 52;
minor = (uint64_t)self->vk_data.rapid.minor << 40; minor = (uint64_t)self->rapid.minor << 40;
out = (u128)(major | minor) << 64; out = (u128)(major | minor) << 64;
break; break;
case SemVer: case SemVer:
major = (uint64_t)self->vk_data.semver.major << 52; major = (uint64_t)self->semver.major << 52;
minor = (uint64_t)self->vk_data.semver.minor << 40; minor = (uint64_t)self->semver.minor << 40;
patch = (uint64_t)self->vk_data.semver.patch << 28; patch = (uint64_t)self->semver.patch << 28;
out = (u128)(major | minor | patch) << 64; out = (u128)(major | minor | patch) << 64;
break; break;
case Extended: case Extended:
major = (uint64_t)self->vk_data.extended.major << 52; major = (uint64_t)self->extended.major << 52;
minor = (uint64_t)self->vk_data.extended.minor << 40; minor = (uint64_t)self->extended.minor << 40;
patch = (uint64_t)self->vk_data.extended.patch << 28; patch = (uint64_t)self->extended.patch << 28;
build = (uint64_t)self->vk_data.extended.build << 16; build = (uint64_t)self->extended.build << 16;
out = (u128)(major | minor | patch | build) << 64; out = (u128)(major | minor | patch | build) << 64;
break; break;
} }
switch (self->pr_tag) { switch (self->pr.tag) {
case Alpha: case Alpha:
out |= ((u128)010000 << 64); out |= ((u128)010000 << 64);
pre = self->pr_data.alpha & mask; pre = self->pr.alpha & mask;
out |= ((u128)pre << 64); out |= ((u128)pre << 64);
break; break;
case Beta: case Beta:
out |= ((u128)020000 << 64); out |= ((u128)020000 << 64);
pre = self->pr_data.beta & mask; pre = self->pr.beta & mask;
out |= ((u128)pre << 64); out |= ((u128)pre << 64);
break; break;
case ReleaseCandidate: case ReleaseCandidate:
out |= ((u128)040000 << 64); out |= ((u128)040000 << 64);
pre = self->pr_data.rc & mask; pre = self->pr.rc & mask;
out |= ((u128)pre << 64); out |= ((u128)pre << 64);
break; break;
case GitRev: case GitRev:
out |= ((u128)01000 << 64); out |= ((u128)01000 << 64);
ts = dateTimeGetTimestamp(&self->pr_data.git->dt); ts = dateTimeGetTimestamp(&self->pr.git.dt);
out |= (u128)ts; out |= (u128)ts;
break; break;
case PRNone: case PRNone:
@ -106,6 +106,83 @@ int parseArch(char *s) {
return -1; return -1;
} }
uint16_t parseU12NonZero(char *vp, long *out) {
long val;
char *ep;
if (vp == NULL) {
*out = 0;
}
val = strtol(vp, &ep, 10);
if (vp != ep && ep == NULL && val <= U12_MAX && val > 0) {
*out = val;
} else {
return 1;
}
return 0;
}
int parseGitRev(char *vp, GitRevision *git) {
char hash[7];
int64_t ts;
char *hash_str, *ts_str, *ep;
int i;
hash_str = strtok(vp, ".");
if (hash_str == NULL) return 1;
if (strnlen(hash_str, 8) != 7) return 2;
for (i = 0; i < 7; i++) {
hash[i] = hash_str[i];
}
ts_str = strtok(NULL, ".");
if (ts_str == NULL) return 1;
ts = strtoll(ts_str, &ep, 10);
if (ts_str == ep || ep != NULL) return 3;
if (strtok(NULL, ".") != NULL) return 4;
for (i = 0; i < 7; i++) {
git->hash[i] = hash[i];
}
dateTimeFromTimestampParts(&git->dt, ts, 0);
return 0;
}
int parsePreRelease(PreRelease *pr, char *s) {
PreReleaseTag tag = PRNone;
long val = 0;
char v[50];
char *vp = (char *)v;
ssize_t len = strnlen(s, 52);
if (len > 50) return 1;
memcpy(s, vp, len);
if (strncasecmp(s, "alpha", 5) == 0) {
tag = Alpha;
vp = s + 5;
if (parseU12NonZero(vp, &val) != 0)
return 1;
pr->alpha = (uint16_t)val;
} else if (strncasecmp(s, "beta", 4) == 0) {
tag = Beta;
vp = s + 4;
if (parseU12NonZero(vp, &val) != 0)
return 1;
pr->beta = (uint16_t)val;
} else if (strncasecmp(s, "rc", 2) == 0) {
tag = ReleaseCandidate;
vp = s + 2;
if (parseU12NonZero(vp, &val) != 0)
return 1;
pr->rc = (uint16_t)val;
} else if (strncasecmp(s, "git_", 4) == 0) {
tag = GitRev;
vp = s + 4;
if (parseGitRev(vp, &pr->git) != 0)
return 1;
}
pr->tag = tag;
return 0;
}
int parseVersion(Version *self, const char *s) { int parseVersion(Version *self, const char *s) {
// todo // todo
return -1; return -1;
@ -123,53 +200,62 @@ char *versionToString(Version *self) {
} }
switch (self->vk_tag) { switch (self->vk_tag) {
case Simple: case Simple:
snprintf(buf, 6, "%d", self->vk_data.simple.major); snprintf(buf, 6, "%d", self->simple.major);
break; break;
case Rapid: case Rapid:
snprintf(buf, 13, "%d.%d", self->vk_data.rapid.major, self->vk_data.rapid.minor); snprintf(buf, 13, "%d.%d", self->rapid.major, self->rapid.minor);
break; break;
case SemVer: case SemVer:
snprintf( snprintf(
buf, 20, "%d.%d.%d", buf, 20, "%d.%d.%d",
self->vk_data.semver.major, self->semver.major,
self->vk_data.semver.minor, self->semver.minor,
self->vk_data.semver.patch self->semver.patch
); );
break; break;
case Extended: case Extended:
snprintf( snprintf(
buf, 27, "%d.%d.%d.%d", buf, 27, "%d.%d.%d.%d",
self->vk_data.extended.major, self->extended.major,
self->vk_data.extended.minor, self->extended.minor,
self->vk_data.extended.patch, self->extended.patch,
self->vk_data.extended.build self->extended.build
); );
break; break;
} }
switch (self->pr_tag) { switch (self->pr.tag) {
case PRNone: case PRNone:
snprintf(temp, 9, "-%s", archToString(self->arch)); snprintf(temp, 9, "-%s", archToString(self->arch));
break; break;
case Alpha: case Alpha:
snprintf(temp, 25, "_alpha%d-%s", self->pr_data.alpha, archToString(self->arch)); if (self->pr.alpha == 0)
snprintf(temp, 25, "_alpha-%s", archToString(self->arch));
else
snprintf(temp, 25, "_alpha%d-%s", self->pr.alpha, archToString(self->arch));
break; break;
case Beta: case Beta:
snprintf(temp, 25, "_beta%d-%s", self->pr_data.beta, archToString(self->arch)); if (self->pr.beta == 0)
snprintf(temp, 25, "_beta-%s", archToString(self->arch));
else
snprintf(temp, 25, "_beta%d-%s", self->pr.beta, archToString(self->arch));
break; break;
case ReleaseCandidate: case ReleaseCandidate:
snprintf(temp, 25, "_rc%d-%s", self->pr_data.rc, archToString(self->arch)); if (self->pr.rc == 0)
snprintf(temp, 25, "_rc-%s", archToString(self->arch));
else
snprintf(temp, 25, "_rc%d-%s", self->pr.rc, archToString(self->arch));
break; break;
case GitRev: case GitRev:
snprintf( snprintf(
temp, 50, "_git_%c%c%c%c%c%c%c.%li-%s", temp, 50, "_git_%c%c%c%c%c%c%c.%li-%s",
self->pr_data.git->hash[0], self->pr.git.hash[0],
self->pr_data.git->hash[1], self->pr.git.hash[1],
self->pr_data.git->hash[2], self->pr.git.hash[2],
self->pr_data.git->hash[3], self->pr.git.hash[3],
self->pr_data.git->hash[4], self->pr.git.hash[4],
self->pr_data.git->hash[5], self->pr.git.hash[5],
self->pr_data.git->hash[6], self->pr.git.hash[6],
dateTimeGetTimestamp(&self->pr_data.git->dt), dateTimeGetTimestamp(&self->pr.git.dt),
archToString(self->arch) archToString(self->arch)
); );
} }

View file

@ -33,74 +33,19 @@
include ../config.mk include ../config.mk
CFLAGS += -I../include CFLAGS += -I../include
LDLIBS += ../libhaggis.a CFLAGS += -I../../libepoch/include
LDLIBS += ../libsemver.a
LIBS += ../../libepoch/libepoch.a
LDLIBS += $(LIBS) LDLIBS += $(LIBS)
tests += store_u16 tests += u128_from_version
tests += load_u16 tests += compare
tests += store_u32
tests += load_u32
tests += store_u64
tests += load_u64
tests += store_header
tests += check_header
tests += store_device
tests += load_device
tests += store_md5
tests += load_md5
tests += store_sha1
tests += load_sha1
tests += store_sha256
tests += load_sha256
tests += init_file_md5
tests += init_file_sha1
tests += init_file_sha256
tests += store_file_md5
tests += load_file_md5
tests += store_file_sha1
tests += load_file_sha1
tests += store_file_sha256
tests += load_file_sha256
tests += fnv1a_hash_inode
tests += fnv1a_hash_str
tests += linkmap_init
tests += linkmap_put
tests += create_dir_node
tests += create_symlink_node
tests += create_fifo_node
tests += create_dev_node
tests += create_file_node
tests += mq_push_pop
tests += extract_dev_node
tests += extract_dir_node
tests += extract_fifo_node
tests += extract_file_node
tests += extract_symlink_node
tests += extract_hardlink_node
total != echo $(tests) | wc -w | awk '{ print $$1 }' total != echo $(tests) | wc -w | awk '{ print $$1 }'
.PHONY: test .PHONY: test
test: $(tests) output test: $(tests) runner output
@echo -e "\n\t=== \e[0;33mRunning $(total) tests\e[0m ===\n" ./runner $(tests)
@idx=1 ; success=0 ; fail=0; skip=0; for t in $(tests) ; \
do printf "[%02i/$(total)] %-25s" $${idx} $${t} ; \
idx=$$(expr $${idx} + 1) ; \
./$${t} ; \
retval=$$? ; \
if [ $${retval} -eq 0 ] ; \
then echo -e '\e[0;32mSuccess\e[0m' ; \
success=$$(expr $${success} + 1) ; \
elif [ $${retval} -eq 255 ] ; \
then echo Skipped ; \
skip=$$(expr $${skip} + 1) ; \
else echo -e '\e[0;31mFailure\e[0m' ; \
fail=$$(expr $${fail} + 1) ; \
fi ; done || true ; \
if [ $${fail} == 0 ] ; \
then echo -e '\nResults: \e[0;32mOk\e[0m.' "$${success} succeeded; $${fail} failed; $${skip} skipped" ; \
else echo -e '\nResults: \e[0;31mFAILED\e[0m.' "$${success} succeeded; $${fail} failed; $${skip} skipped" ; \
fi
output: output:
@ [-d $@ ] 2>/dev/null || install -d $@ @ [-d $@ ] 2>/dev/null || install -d $@

31
test/compare.c Normal file
View file

@ -0,0 +1,31 @@
#include "semver.h"
#include <assert.h>
int main() {
Version a, b;
a.vk_tag = SemVer;
a.semver.major = 3;
a.semver.minor = 14;
a.semver.patch = 0;
a.pr.tag = PRNone;
a.arch = x86_64;
b.vk_tag = Rapid;
b.rapid.major = 3;
b.rapid.minor = 14;
b.pr.tag = PRNone;
b.arch = x86_64;
assert(compareVersion(&a, &b) == CompEqual);
b.pr.tag = Alpha;
b.pr.alpha = 1;
assert(compareVersion(&a, &b) == CompGreater);
a.pr.tag = Beta;
a.pr.beta = 2;
assert(compareVersion(&a, &b) == CompGreater);
b.pr.tag = ReleaseCandidate;
b.pr.rc = 1;
assert(compareVersion(&a, &b) == CompLess);
a.arch = arm64;
assert(compareVersion(&a, &b) == CompNone);
return 0;
}

45
test/runner.c Normal file
View file

@ -0,0 +1,45 @@
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
int i, pass = 0, fail = 0, skip = 0, total, ret, read = 0;
FILE *pipe;
char cmd[100], output[500];
total = argc-1;
printf("\n\t=== \033[0;33mRunning %i tests\033[0m ===\n", total);
for (i = 1; i < argc; i++) {
snprintf(cmd, 100, "./%s", argv[i]);
printf("%-25s", argv[i]);
pipe = popen(cmd, "w");
read = fread(&output, 1, 500, pipe);
ret = pclose(pipe);
if (read) {
fail++;
printf("\033[0;31mFailed\033[0m\n");
fprintf(stderr, "%s\n", &output);
continue;
}
switch (WEXITSTATUS(ret)) {
case 0:
pass++;
printf("\033[0;32mSuccess\033[0m\n");
break;
case 255:
skip++;
printf("Skipped\n");
break;
default:
fail++;
printf("\033[0;31mFailed\033[0m\n");
}
}
if (fail) {
printf("\nResults: \033[0;31mFAILED\033[0m %i succeeded; %i failed; %i skipped\n",
pass, fail, skip);
} else {
printf("\nResults: \033[0;32mOk\033[0m %i succeeded; %i failed; %i skipped\n",
pass, fail, skip);
}
return 0;
}

19
test/u128_from_version.c Normal file
View file

@ -0,0 +1,19 @@
#include "semver.h"
#include <assert.h>
int main() {
Version v;
u128 n;
uint64_t sn;
v.vk_tag = SemVer;
v.semver.major = 3;
v.semver.minor = 14;
v.semver.patch = 0;
v.pr.tag = PRNone;
v.arch = x86_64;
n = u128FromVersion(&v);
sn = (uint64_t)(n >> 64);
assert(sn == 0600340000000100000);
return 0;
}