Fix some version ordering bugs.

- Use @total_ordering to supply implementations of all other comparison
  functions from just __eq__() and __le__().  This is not done automatically.

  https://docs.python.org/3/library/functools.html#functools.total_ordering

- An incomplete check for "pre" meant that the following would evaluate
  to true:

      VersionChunk("1pre2") < VersionChunk("1pre2")

  In practice this wasn't a problem because it appears that when comparing
  VersionChunk lists, equality is checked before ordering, but this isn't
  good to rely on.
This commit is contained in:
Bryan Gardiner 2024-08-18 17:39:11 -07:00
parent 8c8c662d87
commit 9594ab31ef
No known key found for this signature in database
GPG key ID: 53EFBCA063E6183C

View file

@ -43,6 +43,7 @@ import os.path
import re import re
import subprocess import subprocess
import sys import sys
from functools import total_ordering
from pathlib import Path from pathlib import Path
from signal import SIGPIPE, SIG_DFL, signal from signal import SIGPIPE, SIG_DFL, signal
from subprocess import PIPE from subprocess import PIPE
@ -127,6 +128,7 @@ class StorePath:
# For the version comparison algorithm, see: # For the version comparison algorithm, see:
# https://nix.dev/manual/nix/stable/command-ref/nix-env/upgrade#versions # https://nix.dev/manual/nix/stable/command-ref/nix-env/upgrade#versions
@total_ordering
class VersionChunk: class VersionChunk:
def __init__(self, chunk_value: Union[int, str]): def __init__(self, chunk_value: Union[int, str]):
assert isinstance(chunk_value, int) or isinstance(chunk_value, str) assert isinstance(chunk_value, int) or isinstance(chunk_value, str)
@ -145,9 +147,9 @@ class VersionChunk:
y_int = isinstance(y, int) y_int = isinstance(y, int)
if x_int and y_int: if x_int and y_int:
return x < y return x < y
elif (x == "" and y_int) or x == "pre": elif (x == "" and y_int) or (x == "pre" and y != "pre"):
return True return True
elif (x_int and y == "") or y == "pre": elif (x_int and y == "") or (y == "pre" and x != "pre"):
return False return False
elif x_int: # y is a string elif x_int: # y is a string
return False return False
@ -162,6 +164,7 @@ class VersionChunk:
return self._chunk_value == other._chunk_value return self._chunk_value == other._chunk_value
@total_ordering
class Version: class Version:
def __init__(self, text: Optional[str]): def __init__(self, text: Optional[str]):
if text is None: if text is None: