From bcf4faa5f261d50f55b00dab2738087ac9e97c5c Mon Sep 17 00:00:00 2001
From: Simon Rose <simon.rose@ess.eu>
Date: Thu, 17 Nov 2022 17:22:58 +0100
Subject: [PATCH] Add revision number comparison to test versions

---
 require-ess/src/require.c | 55 +++++++++++++++++++++------------------
 require-ess/src/version.c | 11 +++++---
 tests/test_versions.py    |  6 +++++
 3 files changed, 43 insertions(+), 29 deletions(-)

diff --git a/require-ess/src/require.c b/require-ess/src/require.c
index d07c7d4d..af42436a 100644
--- a/require-ess/src/require.c
+++ b/require-ess/src/require.c
@@ -617,8 +617,7 @@ static int compareDigit(int found, int requested, const char *name) {
   return MATCH;
 }
 
-static int compareNumericVersion(semver_t *sv_found, semver_t *sv_request,
-                                 int already_matched) {
+static int compareNumericVersion(semver_t *sv_found, semver_t *sv_request) {
   int match;
 
   match = compareDigit(sv_found->major, sv_request->major, "major");
@@ -629,25 +628,7 @@ static int compareNumericVersion(semver_t *sv_found, semver_t *sv_request,
   if (match != MATCH) {
     return match;
   }
-  match = compareDigit(sv_found->patch, sv_request->patch, "patch");
-  if (match != MATCH) {
-    return match;
-  }
-
-  if (sv_request->revision == -1) {
-    if (already_matched) {
-      debug(
-          "require: compareVersions: No revision number requested. Returning "
-          "HIGHER\n");
-      return HIGHER;
-    } else {
-      debug(
-          "require: compareVersions: No revision number requested. Returning "
-          "MATCH\n");
-      return MATCH;
-    }
-  }
-  return compareDigit(sv_found->revision, sv_request->revision, "revision");
+  return compareDigit(sv_found->patch, sv_request->patch, "patch");
 }
 
 /*
@@ -672,16 +653,20 @@ static int compareVersions(const char *found, const char *request,
   sv_found = (semver_t *)calloc(1, sizeof(semver_t));
   sv_request = (semver_t *)calloc(1, sizeof(semver_t));
 
+  // TODO: maybe don't do this. This is only in the case that
+  // we have found an installed version with no revision number.
+  if (already_matched && sv_request->revision == -1) sv_request->revision = 0;
+
   parse_semver(found, sv_found);
   parse_semver(request, sv_request);
 
   // test version, look for exact.
   if (strlen(sv_request->test_str) > 0) {
-    if (strcmp(found, request) == 0) {
+    if (strcmp(sv_found->test_str, sv_request->test_str) == 0) {
       debug(
           "require: compareVersions: Test version requested and found, "
-          "matches exactly\n");
-      match = EXACT;
+          "matches\n");
+      match = MATCH;
     } else if (strlen(sv_found->test_str) > 0) {
       debug(
           "require: compareVersions: Test versions requested and found, no "
@@ -699,7 +684,27 @@ static int compareVersions(const char *found, const char *request,
         "found\n");
     match = MISMATCH;
   } else {
-    match = compareNumericVersion(sv_found, sv_request, already_matched);
+    match = compareNumericVersion(sv_found, sv_request);
+  }
+
+  // Finally, check revision numbers
+  if (match == MATCH) {
+    if (sv_request->revision == -1) {
+      if (already_matched) {
+        debug(
+            "require: compareVersions: No revision number for already found "
+            "version. Returning HIGHER\n");
+        match = HIGHER;
+      } else {
+        debug(
+            "require: compareVersions: No revision number requested. Returning "
+            "MATCH\n");
+        match = MATCH;
+      }
+    } else {
+      match =
+          compareDigit(sv_found->revision, sv_request->revision, "revision");
+    }
   }
   cleanup_semver(sv_found);
   cleanup_semver(sv_request);
diff --git a/require-ess/src/version.c b/require-ess/src/version.c
index a57587f5..c0867a4a 100644
--- a/require-ess/src/version.c
+++ b/require-ess/src/version.c
@@ -35,13 +35,13 @@ static void fetch_part(char *version_str, const regmatch_t *groups,
 
 int parse_semver(const char *version, semver_t *s) {
   static const char *version_regex =
-      "^(([0-9]+)\\.([0-9]+)\\.([0-9]+)(\\+([0-9]+))?)?(.*)$";
+      "^(([0-9]+)\\.([0-9]+)\\.([0-9]+))?([^+]*)(\\+([0-9]+))?$";
   static const unsigned int max_regex_groups = 7 + 1;
   static const unsigned int major_ix = 2;
   static const unsigned int minor_ix = 3;
   static const unsigned int patch_ix = 4;
-  static const unsigned int revision_ix = 6;
-  static const unsigned int test_ix = 7;
+  static const unsigned int test_label_ix = 5;
+  static const unsigned int revision_ix = 7;
 
   regex_t compiled;
   regmatch_t groups[max_regex_groups];
@@ -60,10 +60,13 @@ int parse_semver(const char *version, semver_t *s) {
     fetch_part(s->version_str, groups, major_ix, &s->major);
     fetch_part(s->version_str, groups, minor_ix, &s->minor);
     fetch_part(s->version_str, groups, patch_ix, &s->patch);
+    if (groups[test_label_ix].rm_so != groups[test_label_ix].rm_eo) {
+      s->test_str = s->version_str;
+    }
     if (groups[revision_ix].rm_so != (regoff_t)-1) {
       fetch_part(s->version_str, groups, revision_ix, &s->revision);
+      s->version_str[groups[revision_ix].rm_so - 1] = 0;
     }
-    s->test_str = s->version_str + groups[test_ix].rm_so;
   }
   return 0;
 }
diff --git a/tests/test_versions.py b/tests/test_versions.py
index 19baca42..055684e6 100644
--- a/tests/test_versions.py
+++ b/tests/test_versions.py
@@ -32,6 +32,12 @@ RE_MODULE_NOT_LOADED = "Module {module} (not available|version {required} not av
         ("", "0.0.1+0", ["0.0.1+0", "1.0"]),
         # Numeric version should be prioritised over "higher" test version
         ("", "0.1.0+0", ["0.1.0+0", "1.0.0-rc1"]),
+        # Pick the latest test version based on revision number
+        ("test", "test+2", ["test+0", "test+2"]),
+        # Don't install an incorrect test version
+        ("foo", "", ["bar"]),
+        # Mix of test and numeric
+        ("1.2.3-test", "1.2.3-test+2", ["1.2.3-test", "1.2.3-test+0", "1.2.3-test+2"]),
     ],
 )
 def test_version(wrapper: Wrapper, requested, expected, installed):
-- 
GitLab