Initial commit
This commit is contained in:
@@ -0,0 +1,9 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
.chinook/
|
||||||
|
.environ/
|
||||||
|
.vscode/
|
||||||
+32
@@ -0,0 +1,32 @@
|
|||||||
|
name: version
|
||||||
|
gpid: malunal
|
||||||
|
semv: 1.0.0
|
||||||
|
|
||||||
|
requires:
|
||||||
|
- remote: git@git.erasit.com:malunal/microtest
|
||||||
|
branch: v1.0.0
|
||||||
|
- remote: git@git.erasit.com:malunal/strview
|
||||||
|
branch: v1.0.0
|
||||||
|
- remote: git@git.erasit.com:malunal/types
|
||||||
|
branch: v1.0.0
|
||||||
|
|
||||||
|
targets:
|
||||||
|
- name: malunal.version
|
||||||
|
type: archive
|
||||||
|
deps:
|
||||||
|
- malunal.strview
|
||||||
|
- malunal.types
|
||||||
|
srcs:
|
||||||
|
- ./sources/version.c
|
||||||
|
|
||||||
|
tests:
|
||||||
|
- name: malunal.version.tests
|
||||||
|
type: program
|
||||||
|
deps:
|
||||||
|
- malunal.microtest
|
||||||
|
- malunal.version
|
||||||
|
srcs:
|
||||||
|
- ./tests/version.c
|
||||||
|
|
||||||
|
exports:
|
||||||
|
- malunal.version
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
/**
|
||||||
|
* @file version.h
|
||||||
|
* @brief Contains the structure and functions for a semantic version.
|
||||||
|
* @author John Christman (sorakatadzuma@gmail.com)
|
||||||
|
* @copyright Malunal Studios, LLC.
|
||||||
|
*/
|
||||||
|
#include "malunal/strview.h"
|
||||||
|
#include "malunal/types/error.h"
|
||||||
|
|
||||||
|
#ifndef MALUNAL_VERSION_HEADER
|
||||||
|
#define MALUNAL_VERSION_HEADER
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Imports the @c version_t error domain for error checking.
|
||||||
|
* @details The @c version_t error domain is specific for versions to indicate
|
||||||
|
* an issue with the parsing of a semantic version string into the
|
||||||
|
* @c version_t object.
|
||||||
|
*/
|
||||||
|
extern
|
||||||
|
const error_domain_t
|
||||||
|
ERROR_DOMAIN_VERSION_T;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Contains information for a semantic version.
|
||||||
|
* @details Allows a semantic version to be parsed from some string and its
|
||||||
|
* content stored in this structure.
|
||||||
|
*/
|
||||||
|
define_struct(version) {
|
||||||
|
malunal_size_t major;
|
||||||
|
malunal_size_t minor;
|
||||||
|
malunal_size_t patch;
|
||||||
|
strview_t preview;
|
||||||
|
strview_t build;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Defines a set of error codes for @c version_t.
|
||||||
|
* @details
|
||||||
|
*/
|
||||||
|
typedef enum {
|
||||||
|
VERSION_ERROR_NULL_INPUT,
|
||||||
|
VERSION_ERROR_INVALID_NUMBER,
|
||||||
|
VERSION_ERROR_UNEXPECTED_CHAR,
|
||||||
|
VERSION_ERROR_MISSING_PART,
|
||||||
|
VERSION_ERROR_LEADING_ZERO,
|
||||||
|
VERSION_ERROR_VALUE_OVERFLOW,
|
||||||
|
} version_error_t;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Parses a version string into a @c version_t object.
|
||||||
|
* @param verstr The string containing the semantic version to be parsed.
|
||||||
|
* @param outver A pointer to the version to populate with the result.
|
||||||
|
* @returns An error if the semantic version string could not be parsed into the
|
||||||
|
* @c version_t object.
|
||||||
|
*/
|
||||||
|
error_t
|
||||||
|
parse_version(
|
||||||
|
strview_iptr_t verstr,
|
||||||
|
version_mptr_t outver
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Compares one @c version_t to another.
|
||||||
|
* @param self The @c version_t to compare against.
|
||||||
|
* @param other The @c version_t to compare with.
|
||||||
|
* @retval -1 if self is less than other.
|
||||||
|
* @retval 0 if self is equal to other.
|
||||||
|
* @retval 1 if self is more than other.
|
||||||
|
*/
|
||||||
|
malunal_int32_t
|
||||||
|
version_compare(
|
||||||
|
version_iptr_t self,
|
||||||
|
version_iptr_t other
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Converts the provided @c version_t to a string.
|
||||||
|
* @param self A pointer to the version object to convert to a string.
|
||||||
|
* @param buffer A pointer to where to write the semantic version string.
|
||||||
|
* @param length The length of the buffer which will receive the string.
|
||||||
|
* @returns The length of the version written or -1 if the buffer was too small.
|
||||||
|
*/
|
||||||
|
malunal_int32_t
|
||||||
|
version_string(
|
||||||
|
version_iptr_t self,
|
||||||
|
malunal_str_t buffer,
|
||||||
|
malunal_size_t length
|
||||||
|
);
|
||||||
|
|
||||||
|
#endif /* MALUNAL_VERSION_HEADER */
|
||||||
@@ -0,0 +1,446 @@
|
|||||||
|
#include <ctype.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <memory.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "malunal/version.h"
|
||||||
|
|
||||||
|
|
||||||
|
static
|
||||||
|
malunal_cstr_t
|
||||||
|
describe(malunal_int32_t code) {
|
||||||
|
switch (code) {
|
||||||
|
case VERSION_ERROR_NULL_INPUT:
|
||||||
|
return "Version input or output pointer was null.";
|
||||||
|
case VERSION_ERROR_INVALID_NUMBER:
|
||||||
|
return "Expected a numeric version component but found none, or trailing "
|
||||||
|
"characters remained after it.";
|
||||||
|
case VERSION_ERROR_UNEXPECTED_CHAR:
|
||||||
|
return "An unexpected character was encountered while parsing the version.";
|
||||||
|
case VERSION_ERROR_MISSING_PART:
|
||||||
|
return "The version string was missing one of its required parts.";
|
||||||
|
case VERSION_ERROR_LEADING_ZERO:
|
||||||
|
return "A numeric version component had a disallowed leading zero.";
|
||||||
|
case VERSION_ERROR_VALUE_OVERFLOW:
|
||||||
|
return "A numeric version component was too large to represent.";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Unknown version error";
|
||||||
|
}
|
||||||
|
|
||||||
|
const error_domain_t ERROR_DOMAIN_VERSION_T = {
|
||||||
|
.describe = &describe,
|
||||||
|
.name = "malunal.version.error"
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static
|
||||||
|
error_t
|
||||||
|
parse_core_number(
|
||||||
|
strview_mptr_t verstr,
|
||||||
|
malunal_size_t* outval
|
||||||
|
) {
|
||||||
|
if (!isdigit((unsigned char)*verstr->beg))
|
||||||
|
return (error_t) {
|
||||||
|
.domain = &ERROR_DOMAIN_VERSION_T,
|
||||||
|
.code = VERSION_ERROR_INVALID_NUMBER
|
||||||
|
};
|
||||||
|
|
||||||
|
malunal_cstr_t start = verstr->beg;
|
||||||
|
while (isdigit((unsigned char)*verstr->beg))
|
||||||
|
verstr->beg++;
|
||||||
|
|
||||||
|
if (*verstr->beg != '.')
|
||||||
|
return (error_t) {
|
||||||
|
.domain = &ERROR_DOMAIN_VERSION_T,
|
||||||
|
.code = !(
|
||||||
|
strview_length(verstr) == 0 ||
|
||||||
|
*verstr->beg == '-' ||
|
||||||
|
*verstr->beg == '+' ||
|
||||||
|
*verstr->beg == '.'
|
||||||
|
) ? VERSION_ERROR_UNEXPECTED_CHAR
|
||||||
|
: VERSION_ERROR_MISSING_PART
|
||||||
|
};
|
||||||
|
|
||||||
|
if (verstr->beg - start > 1 && start[0] == '0')
|
||||||
|
return (error_t) {
|
||||||
|
.domain = &ERROR_DOMAIN_VERSION_T,
|
||||||
|
.code = VERSION_ERROR_LEADING_ZERO
|
||||||
|
};
|
||||||
|
|
||||||
|
strview_t walked = { start, verstr->beg };
|
||||||
|
malunal_int64_t value = 0;
|
||||||
|
error_t result = strview_to_int64(&walked, &value);
|
||||||
|
if (result.domain == &ERROR_DOMAIN_STRVIEW_T) {
|
||||||
|
if (result.code == STRVIEW_ERROR_OUT_OF_RANGE)
|
||||||
|
return (error_t) {
|
||||||
|
.domain = &ERROR_DOMAIN_VERSION_T,
|
||||||
|
.code = VERSION_ERROR_VALUE_OVERFLOW
|
||||||
|
};
|
||||||
|
|
||||||
|
if (result.code == STRVIEW_ERROR_UNCONSUMED)
|
||||||
|
return (error_t) {
|
||||||
|
.domain = &ERROR_DOMAIN_VERSION_T,
|
||||||
|
.code = VERSION_ERROR_INVALID_NUMBER
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
*outval = (malunal_size_t)value;
|
||||||
|
return NO_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
error_t
|
||||||
|
parse_patch_number(
|
||||||
|
strview_mptr_t verstr,
|
||||||
|
malunal_size_t* outval
|
||||||
|
) {
|
||||||
|
if (!isdigit((unsigned char)*verstr->beg))
|
||||||
|
return (error_t) {
|
||||||
|
.domain = &ERROR_DOMAIN_VERSION_T,
|
||||||
|
.code = VERSION_ERROR_INVALID_NUMBER
|
||||||
|
};
|
||||||
|
|
||||||
|
malunal_cstr_t start = verstr->beg;
|
||||||
|
while (isdigit((unsigned char)*verstr->beg))
|
||||||
|
verstr->beg++;
|
||||||
|
|
||||||
|
if (
|
||||||
|
strview_length(verstr) != 0 &&
|
||||||
|
*verstr->beg != '-' &&
|
||||||
|
*verstr->beg != '+'
|
||||||
|
) return (error_t) {
|
||||||
|
.domain = &ERROR_DOMAIN_VERSION_T,
|
||||||
|
.code = VERSION_ERROR_UNEXPECTED_CHAR
|
||||||
|
};
|
||||||
|
|
||||||
|
if (verstr->beg - start > 1 && start[0] == '0')
|
||||||
|
return (error_t) {
|
||||||
|
.domain = &ERROR_DOMAIN_VERSION_T,
|
||||||
|
.code = VERSION_ERROR_LEADING_ZERO
|
||||||
|
};
|
||||||
|
|
||||||
|
strview_t walked = { start, verstr->beg };
|
||||||
|
malunal_int64_t value = 0;
|
||||||
|
error_t result = strview_to_int64(&walked, &value);
|
||||||
|
if (result.domain == &ERROR_DOMAIN_STRVIEW_T) {
|
||||||
|
if (result.code == STRVIEW_ERROR_OUT_OF_RANGE)
|
||||||
|
return (error_t) {
|
||||||
|
.domain = &ERROR_DOMAIN_VERSION_T,
|
||||||
|
.code = VERSION_ERROR_VALUE_OVERFLOW
|
||||||
|
};
|
||||||
|
|
||||||
|
if (result.code == STRVIEW_ERROR_UNCONSUMED)
|
||||||
|
return (error_t) {
|
||||||
|
.domain = &ERROR_DOMAIN_VERSION_T,
|
||||||
|
.code = VERSION_ERROR_INVALID_NUMBER
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
*outval = (malunal_size_t)value;
|
||||||
|
return NO_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
error_t
|
||||||
|
parse_preview_string(
|
||||||
|
strview_mptr_t verstr,
|
||||||
|
strview_mptr_t outstr
|
||||||
|
) {
|
||||||
|
if (*verstr->beg != '-')
|
||||||
|
return NO_ERROR;
|
||||||
|
|
||||||
|
malunal_cstr_t start = ++verstr->beg;
|
||||||
|
if (strview_length(verstr) == 0 || *verstr->beg == '+')
|
||||||
|
return (error_t) {
|
||||||
|
.domain = &ERROR_DOMAIN_VERSION_T,
|
||||||
|
.code = VERSION_ERROR_MISSING_PART
|
||||||
|
};
|
||||||
|
|
||||||
|
while (strview_length(verstr) && *verstr->beg != '+')
|
||||||
|
verstr->beg++;
|
||||||
|
|
||||||
|
*outstr = (strview_t) {
|
||||||
|
.beg = start,
|
||||||
|
.end = verstr->beg
|
||||||
|
};
|
||||||
|
return NO_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
error_t
|
||||||
|
parse_build_string(
|
||||||
|
strview_mptr_t verstr,
|
||||||
|
strview_mptr_t outstr
|
||||||
|
) {
|
||||||
|
if (*verstr->beg != '+')
|
||||||
|
return NO_ERROR;
|
||||||
|
|
||||||
|
malunal_cstr_t start = ++verstr->beg;
|
||||||
|
if (strview_length(verstr) == 0)
|
||||||
|
return (error_t) {
|
||||||
|
.domain = &ERROR_DOMAIN_VERSION_T,
|
||||||
|
.code = VERSION_ERROR_MISSING_PART
|
||||||
|
};
|
||||||
|
|
||||||
|
while (strview_length(verstr) != 0)
|
||||||
|
verstr->beg++;
|
||||||
|
|
||||||
|
*outstr = (strview_t) {
|
||||||
|
.beg = start,
|
||||||
|
.end = verstr->beg
|
||||||
|
};
|
||||||
|
|
||||||
|
return NO_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
error_t
|
||||||
|
parse_version(
|
||||||
|
strview_iptr_t verstr,
|
||||||
|
version_mptr_t out
|
||||||
|
) {
|
||||||
|
if (verstr == null)
|
||||||
|
return (error_t) {
|
||||||
|
.domain = &ERROR_DOMAIN_VERSION_T,
|
||||||
|
.code = VERSION_ERROR_NULL_INPUT
|
||||||
|
};
|
||||||
|
|
||||||
|
if (out == null)
|
||||||
|
return (error_t) {
|
||||||
|
.domain = &ERROR_DOMAIN_VERSION_T,
|
||||||
|
.code = VERSION_ERROR_NULL_INPUT
|
||||||
|
};
|
||||||
|
|
||||||
|
memset(out, 0, sizeof(version_t));
|
||||||
|
if (verstr->beg == null)
|
||||||
|
return NO_ERROR;
|
||||||
|
|
||||||
|
error_t result;
|
||||||
|
strview_t tempstr = {
|
||||||
|
.beg = verstr->beg,
|
||||||
|
.end = verstr->end
|
||||||
|
};
|
||||||
|
|
||||||
|
result = parse_core_number(&tempstr, &out->major);
|
||||||
|
if (result.domain != null)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
tempstr.beg++;
|
||||||
|
result = parse_core_number(&tempstr, &out->minor);
|
||||||
|
if (result.domain != null)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
tempstr.beg++;
|
||||||
|
result = parse_patch_number(&tempstr, &out->patch);
|
||||||
|
if (result.domain != null)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
result = parse_preview_string(&tempstr, &out->preview);
|
||||||
|
if (result.domain != null)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
result = parse_build_string(&tempstr, &out->build);
|
||||||
|
if (result.domain != null)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
if (strview_length(&tempstr) != 0)
|
||||||
|
return (error_t) {
|
||||||
|
.domain = &ERROR_DOMAIN_VERSION_T,
|
||||||
|
.code = VERSION_ERROR_UNEXPECTED_CHAR
|
||||||
|
};
|
||||||
|
|
||||||
|
return NO_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
malunal_bool_t
|
||||||
|
identifier_is_numeric(strview_iptr_t id) {
|
||||||
|
if (strview_length(id) == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
malunal_cstr_t p;
|
||||||
|
for (p = id->beg; p < id->end; p++)
|
||||||
|
if (!isdigit(*p))
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
malunal_int32_t
|
||||||
|
compare_numeric_identifier(
|
||||||
|
strview_iptr_t a,
|
||||||
|
strview_iptr_t b
|
||||||
|
) {
|
||||||
|
malunal_size_t alen = strview_length(a);
|
||||||
|
malunal_size_t blen = strview_length(b);
|
||||||
|
malunal_cstr_t ap = a->beg;
|
||||||
|
malunal_cstr_t bp = b->beg;
|
||||||
|
|
||||||
|
// Ignore leading zeros so two differently-padded numbers compare fairly.
|
||||||
|
while (alen > 1 && *ap == '0') { ap++; alen--; }
|
||||||
|
while (blen > 1 && *bp == '0') { bp++; blen--; }
|
||||||
|
|
||||||
|
if (alen != blen) return alen < blen ? -1 : 1;
|
||||||
|
if (alen == 0) return 0;
|
||||||
|
|
||||||
|
malunal_int32_t cmp = strncmp(ap, bp, alen);
|
||||||
|
return cmp < 0 ? -1 : (cmp > 0 ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
malunal_int32_t
|
||||||
|
compare_alphanumeric_identifier(
|
||||||
|
strview_iptr_t a,
|
||||||
|
strview_iptr_t b
|
||||||
|
) {
|
||||||
|
malunal_size_t alen = strview_length(a);
|
||||||
|
malunal_size_t blen = strview_length(b);
|
||||||
|
malunal_size_t minlen = alen < blen ? alen : blen;
|
||||||
|
malunal_int32_t cmp = minlen > 0
|
||||||
|
? strncmp(a->beg, b->beg, (size_t)minlen)
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
if (cmp != 0)
|
||||||
|
return cmp < 0 ? -1 : 1;
|
||||||
|
|
||||||
|
return alen != blen
|
||||||
|
? alen < blen ? -1 : 1
|
||||||
|
: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
malunal_int32_t
|
||||||
|
compare_preview_identifier(
|
||||||
|
strview_iptr_t a,
|
||||||
|
strview_iptr_t b
|
||||||
|
) {
|
||||||
|
malunal_bool_t anum = identifier_is_numeric(a);
|
||||||
|
malunal_bool_t bnum = identifier_is_numeric(b);
|
||||||
|
if (anum && bnum)
|
||||||
|
return compare_numeric_identifier(a, b);
|
||||||
|
|
||||||
|
// Per semver, numeric identifiers always have lower precedence than
|
||||||
|
// alphanumeric ones.
|
||||||
|
return anum == bnum
|
||||||
|
? compare_alphanumeric_identifier(a, b)
|
||||||
|
: anum ? -1 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
malunal_int32_t
|
||||||
|
compare_preview(
|
||||||
|
strview_iptr_t self,
|
||||||
|
strview_iptr_t other
|
||||||
|
) {
|
||||||
|
strview_t sremain = *self;
|
||||||
|
strview_t oremain = *other;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
malunal_bool_t sdone = strview_length(&sremain) == 0;
|
||||||
|
malunal_bool_t odone = strview_length(&oremain) == 0;
|
||||||
|
if (sdone && odone)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
// A pre-release with fewer identifiers has lower precedence, as long as
|
||||||
|
// all the preceding identifiers matched.
|
||||||
|
if (sdone) return -1;
|
||||||
|
if (odone) return 1;
|
||||||
|
|
||||||
|
malunal_cstr_t send = sremain.beg;
|
||||||
|
while (send < sremain.end && *send != '.')
|
||||||
|
send++;
|
||||||
|
strview_t sid = { sremain.beg, send };
|
||||||
|
|
||||||
|
malunal_cstr_t oend = oremain.beg;
|
||||||
|
while (oend < oremain.end && *oend != '.')
|
||||||
|
oend++;
|
||||||
|
strview_t oid = { oremain.beg, oend };
|
||||||
|
|
||||||
|
malunal_int32_t cmp = compare_preview_identifier(&sid, &oid);
|
||||||
|
if (cmp != 0)
|
||||||
|
return cmp;
|
||||||
|
|
||||||
|
sremain.beg = send < sremain.end ? send + 1 : send;
|
||||||
|
oremain.beg = oend < oremain.end ? oend + 1 : oend;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
malunal_int32_t
|
||||||
|
version_compare(
|
||||||
|
version_iptr_t self,
|
||||||
|
version_iptr_t other
|
||||||
|
) {
|
||||||
|
if (self == other) return 0;
|
||||||
|
if (self == null) return -1;
|
||||||
|
if (other == null) return 1;
|
||||||
|
|
||||||
|
if (self->major != other->major) return self->major < other->major ? -1 : 1;
|
||||||
|
if (self->minor != other->minor) return self->minor < other->minor ? -1 : 1;
|
||||||
|
if (self->patch != other->patch) return self->patch < other->patch ? -1 : 1;
|
||||||
|
|
||||||
|
malunal_bool_t self_has_preview = strview_length(&self->preview) != 0;
|
||||||
|
malunal_bool_t other_has_preview = strview_length(&other->preview) != 0;
|
||||||
|
if (self_has_preview != other_has_preview)
|
||||||
|
return self_has_preview ? -1 : 1;
|
||||||
|
|
||||||
|
return self_has_preview
|
||||||
|
? compare_preview(&self->preview, &other->preview)
|
||||||
|
: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
malunal_int32_t
|
||||||
|
version_string(
|
||||||
|
version_iptr_t self,
|
||||||
|
malunal_str_t buffer,
|
||||||
|
malunal_size_t length
|
||||||
|
) {
|
||||||
|
if (self == null || buffer == null || length == 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
malunal_size_t written = snprintf(
|
||||||
|
buffer,
|
||||||
|
length,
|
||||||
|
"%llu.%llu.%llu",
|
||||||
|
self->major,
|
||||||
|
self->minor,
|
||||||
|
self->patch
|
||||||
|
);
|
||||||
|
|
||||||
|
if (written < 0 || written >= length)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
malunal_size_t preview_len =
|
||||||
|
strview_length(&self->preview);
|
||||||
|
if (preview_len > 0) {
|
||||||
|
malunal_int32_t n = snprintf(
|
||||||
|
buffer + written,
|
||||||
|
length - written,
|
||||||
|
"-%.*s",
|
||||||
|
preview_len,
|
||||||
|
self->preview.beg
|
||||||
|
);
|
||||||
|
|
||||||
|
if (n < 0 || (written + n) >= length)
|
||||||
|
return -1;
|
||||||
|
written += n;
|
||||||
|
}
|
||||||
|
|
||||||
|
malunal_size_t build_len =
|
||||||
|
strview_length(&self->build);
|
||||||
|
if (build_len > 0) {
|
||||||
|
malunal_int32_t n = snprintf(
|
||||||
|
buffer + written,
|
||||||
|
length - written,
|
||||||
|
"+%.*s",
|
||||||
|
build_len,
|
||||||
|
self->build.beg
|
||||||
|
);
|
||||||
|
|
||||||
|
if (n < 0 || (written + n) >= length)
|
||||||
|
return -1;
|
||||||
|
written += n;
|
||||||
|
}
|
||||||
|
|
||||||
|
return written;
|
||||||
|
}
|
||||||
+455
@@ -0,0 +1,455 @@
|
|||||||
|
#include "malunal/microtest.h"
|
||||||
|
#include "malunal/version.h"
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------
|
||||||
|
* parse_version(): successful parses
|
||||||
|
* ---------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
MICROTEST(parse_version, basic_triple_succeeds) {
|
||||||
|
strview_t view = strview_from_cstr("1.2.3");
|
||||||
|
version_t ver;
|
||||||
|
error_t err = parse_version(&view, &ver);
|
||||||
|
|
||||||
|
MICROTEST_EXPECT_NULL(err.domain);
|
||||||
|
MICROTEST_EXPECT_EQ(ver.major, (malunal_size_t)1);
|
||||||
|
MICROTEST_EXPECT_EQ(ver.minor, (malunal_size_t)2);
|
||||||
|
MICROTEST_EXPECT_EQ(ver.patch, (malunal_size_t)3);
|
||||||
|
MICROTEST_EXPECT_EQ(strview_length(&ver.preview), (malunal_size_t)0);
|
||||||
|
MICROTEST_EXPECT_EQ(strview_length(&ver.build), (malunal_size_t)0);
|
||||||
|
}
|
||||||
|
|
||||||
|
MICROTEST(parse_version, all_zero_succeeds) {
|
||||||
|
strview_t view = strview_from_cstr("0.0.0");
|
||||||
|
version_t ver;
|
||||||
|
error_t err = parse_version(&view, &ver);
|
||||||
|
|
||||||
|
MICROTEST_EXPECT_NULL(err.domain);
|
||||||
|
MICROTEST_EXPECT_EQ(ver.major, (malunal_size_t)0);
|
||||||
|
MICROTEST_EXPECT_EQ(ver.minor, (malunal_size_t)0);
|
||||||
|
MICROTEST_EXPECT_EQ(ver.patch, (malunal_size_t)0);
|
||||||
|
}
|
||||||
|
|
||||||
|
MICROTEST(parse_version, multi_digit_components_succeed) {
|
||||||
|
strview_t view = strview_from_cstr("123.456.789");
|
||||||
|
version_t ver;
|
||||||
|
error_t err = parse_version(&view, &ver);
|
||||||
|
|
||||||
|
MICROTEST_EXPECT_NULL(err.domain);
|
||||||
|
MICROTEST_EXPECT_EQ(ver.major, (malunal_size_t)123);
|
||||||
|
MICROTEST_EXPECT_EQ(ver.minor, (malunal_size_t)456);
|
||||||
|
MICROTEST_EXPECT_EQ(ver.patch, (malunal_size_t)789);
|
||||||
|
}
|
||||||
|
|
||||||
|
MICROTEST(parse_version, preview_only_succeeds) {
|
||||||
|
strview_t view = strview_from_cstr("1.2.3-alpha");
|
||||||
|
version_t ver;
|
||||||
|
error_t err = parse_version(&view, &ver);
|
||||||
|
|
||||||
|
MICROTEST_EXPECT_NULL(err.domain);
|
||||||
|
MICROTEST_EXPECT_EQ(strview_length(&ver.preview), (malunal_size_t)5);
|
||||||
|
MICROTEST_EXPECT_TRUE(memcmp(ver.preview.beg, "alpha", 5) == 0);
|
||||||
|
MICROTEST_EXPECT_EQ(strview_length(&ver.build), (malunal_size_t)0);
|
||||||
|
}
|
||||||
|
|
||||||
|
MICROTEST(parse_version, dotted_preview_succeeds) {
|
||||||
|
strview_t view = strview_from_cstr("1.2.3-alpha.1");
|
||||||
|
version_t ver;
|
||||||
|
error_t err = parse_version(&view, &ver);
|
||||||
|
|
||||||
|
MICROTEST_EXPECT_NULL(err.domain);
|
||||||
|
MICROTEST_EXPECT_EQ(strview_length(&ver.preview), (malunal_size_t)7);
|
||||||
|
MICROTEST_EXPECT_TRUE(memcmp(ver.preview.beg, "alpha.1", 7) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
MICROTEST(parse_version, build_only_succeeds) {
|
||||||
|
strview_t view = strview_from_cstr("1.2.3+build.5");
|
||||||
|
version_t ver;
|
||||||
|
error_t err = parse_version(&view, &ver);
|
||||||
|
|
||||||
|
MICROTEST_EXPECT_NULL(err.domain);
|
||||||
|
MICROTEST_EXPECT_EQ(strview_length(&ver.preview), (malunal_size_t)0);
|
||||||
|
MICROTEST_EXPECT_EQ(strview_length(&ver.build), (malunal_size_t)7);
|
||||||
|
MICROTEST_EXPECT_TRUE(memcmp(ver.build.beg, "build.5", 7) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
MICROTEST(parse_version, preview_and_build_succeed) {
|
||||||
|
strview_t view = strview_from_cstr("1.2.3-alpha.1+build.5");
|
||||||
|
version_t ver;
|
||||||
|
error_t err = parse_version(&view, &ver);
|
||||||
|
|
||||||
|
MICROTEST_EXPECT_NULL(err.domain);
|
||||||
|
MICROTEST_EXPECT_EQ(ver.major, (malunal_size_t)1);
|
||||||
|
MICROTEST_EXPECT_EQ(ver.minor, (malunal_size_t)2);
|
||||||
|
MICROTEST_EXPECT_EQ(ver.patch, (malunal_size_t)3);
|
||||||
|
MICROTEST_EXPECT_TRUE(memcmp(ver.preview.beg, "alpha.1", 7) == 0);
|
||||||
|
MICROTEST_EXPECT_TRUE(memcmp(ver.build.beg, "build.5", 7) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
MICROTEST(parse_version, single_digit_zero_component_is_not_leading_zero) {
|
||||||
|
/* A lone "0" is a valid component; the leading-zero rule only forbids
|
||||||
|
* *multiple* digits starting with zero (e.g. "01"). */
|
||||||
|
strview_t view = strview_from_cstr("0.2.3");
|
||||||
|
version_t ver;
|
||||||
|
error_t err = parse_version(&view, &ver);
|
||||||
|
|
||||||
|
MICROTEST_EXPECT_NULL(err.domain);
|
||||||
|
MICROTEST_EXPECT_EQ(ver.major, (malunal_size_t)0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------
|
||||||
|
* parse_version(): error cases
|
||||||
|
* ---------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
MICROTEST(parse_version, null_verstr_reports_null_input) {
|
||||||
|
version_t ver;
|
||||||
|
error_t err = parse_version(null, &ver);
|
||||||
|
|
||||||
|
MICROTEST_EXPECT_NOT_NULL(err.domain);
|
||||||
|
MICROTEST_EXPECT_EQ(err.code, (malunal_int32_t)VERSION_ERROR_NULL_INPUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
MICROTEST(parse_version, null_out_reports_null_input) {
|
||||||
|
strview_t view = strview_from_cstr("1.2.3");
|
||||||
|
error_t err = parse_version(&view, null);
|
||||||
|
|
||||||
|
MICROTEST_EXPECT_NOT_NULL(err.domain);
|
||||||
|
MICROTEST_EXPECT_EQ(err.code, (malunal_int32_t)VERSION_ERROR_NULL_INPUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
MICROTEST(parse_version, null_backing_data_succeeds_as_zero_version) {
|
||||||
|
strview_t view = { null, null };
|
||||||
|
version_t ver;
|
||||||
|
error_t err = parse_version(&view, &ver);
|
||||||
|
|
||||||
|
MICROTEST_EXPECT_NULL(err.domain);
|
||||||
|
MICROTEST_EXPECT_EQ(ver.major, (malunal_size_t)0);
|
||||||
|
MICROTEST_EXPECT_EQ(ver.minor, (malunal_size_t)0);
|
||||||
|
MICROTEST_EXPECT_EQ(ver.patch, (malunal_size_t)0);
|
||||||
|
}
|
||||||
|
|
||||||
|
MICROTEST(parse_version, leading_zero_in_major_fails) {
|
||||||
|
strview_t view = strview_from_cstr("01.2.3");
|
||||||
|
version_t ver;
|
||||||
|
error_t err = parse_version(&view, &ver);
|
||||||
|
|
||||||
|
MICROTEST_EXPECT_NOT_NULL(err.domain);
|
||||||
|
MICROTEST_EXPECT_EQ(err.code, (malunal_int32_t)VERSION_ERROR_LEADING_ZERO);
|
||||||
|
}
|
||||||
|
|
||||||
|
MICROTEST(parse_version, leading_zero_in_minor_fails) {
|
||||||
|
strview_t view = strview_from_cstr("1.02.3");
|
||||||
|
version_t ver;
|
||||||
|
error_t err = parse_version(&view, &ver);
|
||||||
|
|
||||||
|
MICROTEST_EXPECT_NOT_NULL(err.domain);
|
||||||
|
MICROTEST_EXPECT_EQ(err.code, (malunal_int32_t)VERSION_ERROR_LEADING_ZERO);
|
||||||
|
}
|
||||||
|
|
||||||
|
MICROTEST(parse_version, leading_zero_in_patch_fails) {
|
||||||
|
strview_t view = strview_from_cstr("1.2.03");
|
||||||
|
version_t ver;
|
||||||
|
error_t err = parse_version(&view, &ver);
|
||||||
|
|
||||||
|
MICROTEST_EXPECT_NOT_NULL(err.domain);
|
||||||
|
MICROTEST_EXPECT_EQ(err.code, (malunal_int32_t)VERSION_ERROR_LEADING_ZERO);
|
||||||
|
}
|
||||||
|
|
||||||
|
MICROTEST(parse_version, missing_minor_and_patch_fails) {
|
||||||
|
strview_t view = strview_from_cstr("1.2");
|
||||||
|
version_t ver;
|
||||||
|
error_t err = parse_version(&view, &ver);
|
||||||
|
|
||||||
|
MICROTEST_EXPECT_NOT_NULL(err.domain);
|
||||||
|
MICROTEST_EXPECT_EQ(err.code, (malunal_int32_t)VERSION_ERROR_MISSING_PART);
|
||||||
|
}
|
||||||
|
|
||||||
|
MICROTEST(parse_version, missing_patch_digits_fails) {
|
||||||
|
strview_t view = strview_from_cstr("1.2.");
|
||||||
|
version_t ver;
|
||||||
|
error_t err = parse_version(&view, &ver);
|
||||||
|
|
||||||
|
MICROTEST_EXPECT_NOT_NULL(err.domain);
|
||||||
|
MICROTEST_EXPECT_EQ(err.code, (malunal_int32_t)VERSION_ERROR_INVALID_NUMBER);
|
||||||
|
}
|
||||||
|
|
||||||
|
MICROTEST(parse_version, fourth_numeric_component_fails) {
|
||||||
|
strview_t view = strview_from_cstr("1.2.3.4");
|
||||||
|
version_t ver;
|
||||||
|
error_t err = parse_version(&view, &ver);
|
||||||
|
|
||||||
|
MICROTEST_EXPECT_NOT_NULL(err.domain);
|
||||||
|
MICROTEST_EXPECT_EQ(err.code, (malunal_int32_t)VERSION_ERROR_UNEXPECTED_CHAR);
|
||||||
|
}
|
||||||
|
|
||||||
|
MICROTEST(parse_version, empty_preview_fails) {
|
||||||
|
strview_t view = strview_from_cstr("1.2.3-");
|
||||||
|
version_t ver;
|
||||||
|
error_t err = parse_version(&view, &ver);
|
||||||
|
|
||||||
|
MICROTEST_EXPECT_NOT_NULL(err.domain);
|
||||||
|
MICROTEST_EXPECT_EQ(err.code, (malunal_int32_t)VERSION_ERROR_MISSING_PART);
|
||||||
|
}
|
||||||
|
|
||||||
|
MICROTEST(parse_version, empty_build_fails) {
|
||||||
|
strview_t view = strview_from_cstr("1.2.3+");
|
||||||
|
version_t ver;
|
||||||
|
error_t err = parse_version(&view, &ver);
|
||||||
|
|
||||||
|
MICROTEST_EXPECT_NOT_NULL(err.domain);
|
||||||
|
MICROTEST_EXPECT_EQ(err.code, (malunal_int32_t)VERSION_ERROR_MISSING_PART);
|
||||||
|
}
|
||||||
|
|
||||||
|
MICROTEST(parse_version, negative_major_fails) {
|
||||||
|
strview_t view = strview_from_cstr("-1.2.3");
|
||||||
|
version_t ver;
|
||||||
|
error_t err = parse_version(&view, &ver);
|
||||||
|
|
||||||
|
MICROTEST_EXPECT_NOT_NULL(err.domain);
|
||||||
|
MICROTEST_EXPECT_EQ(err.code, (malunal_int32_t)VERSION_ERROR_INVALID_NUMBER);
|
||||||
|
}
|
||||||
|
|
||||||
|
MICROTEST(parse_version, non_numeric_components_fail) {
|
||||||
|
strview_t view = strview_from_cstr("a.b.c");
|
||||||
|
version_t ver;
|
||||||
|
error_t err = parse_version(&view, &ver);
|
||||||
|
|
||||||
|
MICROTEST_EXPECT_NOT_NULL(err.domain);
|
||||||
|
MICROTEST_EXPECT_EQ(err.code, (malunal_int32_t)VERSION_ERROR_INVALID_NUMBER);
|
||||||
|
}
|
||||||
|
|
||||||
|
MICROTEST(parse_version, overflowing_major_fails) {
|
||||||
|
strview_t view = strview_from_cstr("99999999999999999999.2.3");
|
||||||
|
version_t ver;
|
||||||
|
error_t err = parse_version(&view, &ver);
|
||||||
|
|
||||||
|
MICROTEST_EXPECT_NOT_NULL(err.domain);
|
||||||
|
MICROTEST_EXPECT_EQ(err.code, (malunal_int32_t)VERSION_ERROR_VALUE_OVERFLOW);
|
||||||
|
}
|
||||||
|
|
||||||
|
MICROTEST(parse_version, overflowing_patch_fails) {
|
||||||
|
strview_t view = strview_from_cstr("1.2.99999999999999999999");
|
||||||
|
version_t ver;
|
||||||
|
error_t err = parse_version(&view, &ver);
|
||||||
|
|
||||||
|
MICROTEST_EXPECT_NOT_NULL(err.domain);
|
||||||
|
MICROTEST_EXPECT_EQ(err.code, (malunal_int32_t)VERSION_ERROR_VALUE_OVERFLOW);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------
|
||||||
|
* version_compare(): core number precedence
|
||||||
|
* ---------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
static
|
||||||
|
version_t
|
||||||
|
must_parse(malunal_cstr_t s) {
|
||||||
|
strview_t view = strview_from_cstr(s);
|
||||||
|
version_t ver;
|
||||||
|
parse_version(&view, &ver);
|
||||||
|
return ver;
|
||||||
|
}
|
||||||
|
|
||||||
|
MICROTEST(version_compare, identical_versions_are_equal) {
|
||||||
|
version_t a = must_parse("1.2.3");
|
||||||
|
version_t b = must_parse("1.2.3");
|
||||||
|
|
||||||
|
MICROTEST_EXPECT_EQ(version_compare(&a, &b), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
MICROTEST(version_compare, is_reflexive) {
|
||||||
|
version_t a = must_parse("1.2.3");
|
||||||
|
|
||||||
|
MICROTEST_EXPECT_EQ(version_compare(&a, &a), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
MICROTEST(version_compare, greater_major_wins) {
|
||||||
|
version_t a = must_parse("2.0.0");
|
||||||
|
version_t b = must_parse("1.9.9");
|
||||||
|
|
||||||
|
MICROTEST_EXPECT_GT(version_compare(&a, &b), 0);
|
||||||
|
MICROTEST_EXPECT_LT(version_compare(&b, &a), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
MICROTEST(version_compare, greater_minor_wins_when_major_equal) {
|
||||||
|
version_t a = must_parse("1.3.0");
|
||||||
|
version_t b = must_parse("1.2.9");
|
||||||
|
|
||||||
|
MICROTEST_EXPECT_GT(version_compare(&a, &b), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
MICROTEST(version_compare, greater_patch_wins_when_major_minor_equal) {
|
||||||
|
version_t a = must_parse("1.2.4");
|
||||||
|
version_t b = must_parse("1.2.3");
|
||||||
|
|
||||||
|
MICROTEST_EXPECT_GT(version_compare(&a, &b), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
MICROTEST(version_compare, numeric_comparison_not_lexical) {
|
||||||
|
/* 1.2.10 > 1.2.9 numerically, even though "10" < "9" lexically. */
|
||||||
|
version_t a = must_parse("1.2.10");
|
||||||
|
version_t b = must_parse("1.2.9");
|
||||||
|
|
||||||
|
MICROTEST_EXPECT_GT(version_compare(&a, &b), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------
|
||||||
|
* version_compare(): pre-release precedence
|
||||||
|
* ---------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
MICROTEST(version_compare, preview_has_lower_precedence_than_release) {
|
||||||
|
version_t preview = must_parse("1.0.0-alpha");
|
||||||
|
version_t release = must_parse("1.0.0");
|
||||||
|
|
||||||
|
MICROTEST_EXPECT_LT(version_compare(&preview, &release), 0);
|
||||||
|
MICROTEST_EXPECT_GT(version_compare(&release, &preview), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
MICROTEST(version_compare, more_preview_identifiers_has_higher_precedence) {
|
||||||
|
version_t shorter = must_parse("1.0.0-alpha");
|
||||||
|
version_t longer = must_parse("1.0.0-alpha.1");
|
||||||
|
|
||||||
|
MICROTEST_EXPECT_LT(version_compare(&shorter, &longer), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
MICROTEST(version_compare, numeric_identifier_has_lower_precedence_than_alphanumeric) {
|
||||||
|
version_t numeric = must_parse("1.0.0-alpha.1");
|
||||||
|
version_t alpha = must_parse("1.0.0-alpha.beta");
|
||||||
|
|
||||||
|
MICROTEST_EXPECT_LT(version_compare(&numeric, &alpha), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
MICROTEST(version_compare, numeric_preview_identifiers_compare_numerically) {
|
||||||
|
/* "10" > "2" numerically, even though "10" < "2" lexically. */
|
||||||
|
version_t ten = must_parse("1.0.0-rc.10");
|
||||||
|
version_t two = must_parse("1.0.0-rc.2");
|
||||||
|
|
||||||
|
MICROTEST_EXPECT_GT(version_compare(&ten, &two), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
MICROTEST(version_compare, alphanumeric_preview_identifiers_compare_lexically) {
|
||||||
|
version_t a = must_parse("1.0.0-alpha");
|
||||||
|
version_t b = must_parse("1.0.0-beta");
|
||||||
|
|
||||||
|
MICROTEST_EXPECT_LT(version_compare(&a, &b), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
MICROTEST(version_compare, identical_previews_are_equal) {
|
||||||
|
version_t a = must_parse("1.0.0-alpha.1");
|
||||||
|
version_t b = must_parse("1.0.0-alpha.1");
|
||||||
|
|
||||||
|
MICROTEST_EXPECT_EQ(version_compare(&a, &b), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------
|
||||||
|
* version_compare(): build metadata is ignored
|
||||||
|
* ---------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
MICROTEST(version_compare, differing_build_metadata_is_ignored) {
|
||||||
|
version_t a = must_parse("1.0.0+build.1");
|
||||||
|
version_t b = must_parse("1.0.0+build.2");
|
||||||
|
|
||||||
|
MICROTEST_EXPECT_EQ(version_compare(&a, &b), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
MICROTEST(version_compare, build_metadata_ignored_with_preview_present) {
|
||||||
|
version_t a = must_parse("1.0.0-alpha+build.1");
|
||||||
|
version_t b = must_parse("1.0.0-alpha+build.2");
|
||||||
|
|
||||||
|
MICROTEST_EXPECT_EQ(version_compare(&a, &b), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------
|
||||||
|
* version_string()
|
||||||
|
* ---------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
MICROTEST(version_string, basic_triple_round_trips) {
|
||||||
|
version_t ver = must_parse("1.2.3");
|
||||||
|
|
||||||
|
malunal_char_t buffer[32];
|
||||||
|
malunal_int32_t n = version_string(&ver, buffer, sizeof(buffer));
|
||||||
|
|
||||||
|
MICROTEST_EXPECT_GT(n, 0);
|
||||||
|
MICROTEST_EXPECT_TRUE(strcmp(buffer, "1.2.3") == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
MICROTEST(version_string, includes_preview) {
|
||||||
|
version_t ver = must_parse("1.2.3-alpha.1");
|
||||||
|
|
||||||
|
malunal_char_t buffer[32];
|
||||||
|
malunal_int32_t n = version_string(&ver, buffer, sizeof(buffer));
|
||||||
|
|
||||||
|
MICROTEST_EXPECT_GT(n, 0);
|
||||||
|
MICROTEST_EXPECT_TRUE(strcmp(buffer, "1.2.3-alpha.1") == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
MICROTEST(version_string, includes_build) {
|
||||||
|
version_t ver = must_parse("1.2.3+build.5");
|
||||||
|
|
||||||
|
malunal_char_t buffer[32];
|
||||||
|
malunal_int32_t n = version_string(&ver, buffer, sizeof(buffer));
|
||||||
|
|
||||||
|
MICROTEST_EXPECT_GT(n, 0);
|
||||||
|
MICROTEST_EXPECT_TRUE(strcmp(buffer, "1.2.3+build.5") == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
MICROTEST(version_string, includes_preview_and_build) {
|
||||||
|
version_t ver = must_parse("1.2.3-alpha.1+build.5");
|
||||||
|
|
||||||
|
malunal_char_t buffer[32];
|
||||||
|
malunal_int32_t n = version_string(&ver, buffer, sizeof(buffer));
|
||||||
|
|
||||||
|
MICROTEST_EXPECT_GT(n, 0);
|
||||||
|
MICROTEST_EXPECT_TRUE(strcmp(buffer, "1.2.3-alpha.1+build.5") == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
MICROTEST(version_string, too_small_buffer_fails) {
|
||||||
|
version_t ver = must_parse("1.2.3-alpha.1+build.5");
|
||||||
|
|
||||||
|
malunal_char_t buffer[5];
|
||||||
|
malunal_int32_t n = version_string(&ver, buffer, sizeof(buffer));
|
||||||
|
|
||||||
|
MICROTEST_EXPECT_EQ(n, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
MICROTEST(version_string, exact_size_buffer_succeeds) {
|
||||||
|
/* "1.2.3" is 5 characters plus a null terminator. */
|
||||||
|
version_t ver = must_parse("1.2.3");
|
||||||
|
|
||||||
|
malunal_char_t buffer[6];
|
||||||
|
malunal_int32_t n = version_string(&ver, buffer, sizeof(buffer));
|
||||||
|
|
||||||
|
MICROTEST_EXPECT_EQ(n, 5);
|
||||||
|
MICROTEST_EXPECT_TRUE(strcmp(buffer, "1.2.3") == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
MICROTEST(version_string, one_byte_too_small_fails) {
|
||||||
|
version_t ver = must_parse("1.2.3");
|
||||||
|
|
||||||
|
malunal_char_t buffer[5];
|
||||||
|
malunal_int32_t n = version_string(&ver, buffer, sizeof(buffer));
|
||||||
|
|
||||||
|
MICROTEST_EXPECT_EQ(n, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
MICROTEST(version_string, null_self_fails) {
|
||||||
|
malunal_char_t buffer[32];
|
||||||
|
malunal_int32_t n = version_string(null, buffer, sizeof(buffer));
|
||||||
|
|
||||||
|
MICROTEST_EXPECT_EQ(n, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
MICROTEST(version_string, null_buffer_fails) {
|
||||||
|
version_t ver = must_parse("1.2.3");
|
||||||
|
malunal_int32_t n = version_string(&ver, null, 32);
|
||||||
|
|
||||||
|
MICROTEST_EXPECT_EQ(n, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
MICROTEST(version_string, zero_length_fails) {
|
||||||
|
version_t ver = must_parse("1.2.3");
|
||||||
|
malunal_char_t buffer[32];
|
||||||
|
malunal_int32_t n = version_string(&ver, buffer, 0);
|
||||||
|
|
||||||
|
MICROTEST_EXPECT_EQ(n, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
MICROTEST_MAIN()
|
||||||
Reference in New Issue
Block a user