From 97bd5911ef624d603751750f6749da97ab51b5a1 Mon Sep 17 00:00:00 2001 From: edwmurph Date: Mon, 9 Apr 2018 17:25:30 -0400 Subject: [PATCH 01/12] Dynamically interpret semver expression in local dirs package.json --- nvm.sh | 378 +++++++++++++++++- semver.md | 50 +++ .../Unit tests/nvm_get_node_from_pkg_json | 242 +++++++++++ .../Unit tests/nvm_interpret_complex_semver | 36 ++ test/fast/Unit tests/nvm_string_equals_regexp | 42 ++ test/fast/Unit tests/nvm_validate_semver | 54 +++ .../_invalid_missing_engines | 6 + .../_invalid_missing_node | 9 + .../_invalid_missing_quotes | 9 + .../_valid_with_extra_bracket | 9 + .../package_json_templates/_valid_with_spaces | 9 + .../package_json_templates/_valid_with_tabs | 9 + .../_valid_with_tabs_in_semver | 9 + 13 files changed, 857 insertions(+), 5 deletions(-) create mode 100644 semver.md create mode 100755 test/fast/Unit tests/nvm_get_node_from_pkg_json create mode 100755 test/fast/Unit tests/nvm_interpret_complex_semver create mode 100755 test/fast/Unit tests/nvm_string_equals_regexp create mode 100755 test/fast/Unit tests/nvm_validate_semver create mode 100644 test/fast/Unit tests/package_json_templates/_invalid_missing_engines create mode 100644 test/fast/Unit tests/package_json_templates/_invalid_missing_node create mode 100644 test/fast/Unit tests/package_json_templates/_invalid_missing_quotes create mode 100644 test/fast/Unit tests/package_json_templates/_valid_with_extra_bracket create mode 100644 test/fast/Unit tests/package_json_templates/_valid_with_spaces create mode 100644 test/fast/Unit tests/package_json_templates/_valid_with_tabs create mode 100644 test/fast/Unit tests/package_json_templates/_valid_with_tabs_in_semver diff --git a/nvm.sh b/nvm.sh index 8972e2ac69..d590c9fa27 100644 --- a/nvm.sh +++ b/nvm.sh @@ -299,6 +299,357 @@ nvm_find_up() { nvm_echo "${path_}" } +nvm_string_contains_regexp() { + local string + string="${1-}" + local regexp + regexp="${2-}" + if [ -z "${string-}" ] || [ -z "${regexp-}" ]; then + return 1 + fi + # e.g. "nvm_string_contains_regexp abbc ^aa?b+.$" returns 0 + command printf "%s" "$string" | command awk "/$regexp/{ exit 0 }{ exit 1 }" +} + +# scoped function returns nothing or a normalized semver adhering to the following grammar: +# +# semver ::= comparator_set ( ' || ' comparator_set )* +# comparator_set ::= comparator ( ' ' comparator )* +# comparator ::= ( '<' | '<=' | '>' | '>=' | '' ) [0-9]+ '.' [0-9]+ '.' [0-9]+ +nvm_validate_semver() { ( + semver=$(command printf "%s" "${1-}" | command tr '||' '\n') + [ -n "$semver" ] || return 1 + validated_semver=''; + while [ -n "$semver" ]; do + comparator_set=$(command printf "%s" "$semver" | head -n1) + semver=$(command printf "%s" "$semver" | tail -n +2) + [ -n "$comparator_set" ] || continue + + # convert comparators into required grammar + validated_comparator_set=$(command printf " %s " "$comparator_set" | + # exactly 1 space is needed before and after every comparator (including the first and last comparators) + command sed -E 's/\011/ /g;s/ +/ /g' | + + # normalize all wildcards to `x` + command sed -E 's/X|\*/x/g' | + + # ` 1 ` => ` 1.x.x ` + # ` x ` => ` x.x.x ` + command sed -E 's/ ([0-9]+|x) / \1.x.x /g' | + + # ` 1.2 ` => ` 1.2.x ` + # ` 1.x ` => ` 1.x.x ` + # ` x.x ` => ` x.x.x ` + command sed -E 's/ (([0-9]+|x)\.([0-9]+|x)) / \1.x /g' | + + # ` 1.2.3 - 1.2.4 ` => ` >=1.2.3 <=1.2.4 ` + command sed -E 's/ (([0-9]+|x)\.([0-9]+|x)\.([0-9]+|x)) ?\- ?(([0-9]+|x)\.([0-9]+|x)\.([0-9]+|x)) / >=\1 <=\5 /g' | + + # ` > 1.2.3 ` => ` >1.2.3 ` + # ` < 1.2.5 ` => ` <1.2.5 ` + # ` <= 1.2.3 ` => ` <=1.2.3 ` + # ` >= 1.2.3 ` => ` >=1.2.3 ` + # ` = 1.2.3 ` => ` =1.2.3 ` + # ` ~ 1.2.3 ` => ` ~1.2.3 ` + # ` ^ 1.2.3 ` => ` ^1.2.3 ` + command sed -E 's/ (<|>|<=|>=|=|~|\^) (([0-9]+|x)\.([0-9]+|x)\.([0-9]+|x)) / \1\2 /g' | + + # ` =1.2.3 ` => ` 1.2.3 ` + command sed -E 's/ =//g' | + + # trim leading/trailing spaces + command sed -E 's/^ //;s/ $//' | + + # handle conversions of comparators with '^' or '~' or 'x' into required grammar + # ` ^0.0.1 ` => ` >=0.0.1 <0.0.2 ` + # ` ^0.1.2 ` => ` >=0.1.2 <0.2.0 ` + # ` ^1.2.3 ` => ` >=1.2.3 <2.0.0 ` + # ` ~0.0.1 ` => ` >=0.0.1 <0.1.0 ` + # ` ~0.1.2 ` => ` >=0.1.2 <0.2.0 ` + # ` ~1.2.3 ` => ` >=1.2.3 <1.3.0 ` + # ` x.x.x ` => ` >0.0.0 ` + # ` x.x.1 ` => ` >0.0.0 ` + # ` x.1.x ` => ` >0.0.0 ` + # ` x.1.2 ` => ` >0.0.0 ` + # ` 1.2.x ` => ` >=1.2.0 <1.3.0 ` + # ` 1.x.1 ` => ` >=1.0.0 <2.0.0 ` NOTE the last "1" is ignored in this grammar + # ` 1.x.x ` => ` >=1.0.0 <2.0.0 ` + command awk '{ + if ( ! match($0, /[\^~x]/) ) { + print $0 + exit 0 + } + split($0, comparators, / /) + output="" + for (i = 1; i <= length(comparators); i++) { + comparator=comparators[i] + if ( match(comparator, /^([\^~>]|>=)?x.[0-9x]+.[0-9x]$/ ) ) { + comparator=">0.0.0" + } else if ( match(comparator, /^([\^~<>]|>=|<=)?[0-9]+.x.[0-9x]$/) ) { + split(comparator, a, /\./); + if ( match(comparator, /^([\^~<>]|>=|<=).*$/ ) ) { + comparator=a[1] ".0.0"; + } else { + comparator=">=" a[1] ".0.0 <" a[1]+1 ".0.0" + } + } else if ( match(comparator, /^([\^~<>]|<=|>=)?[0-9]+.[0-9]+.x$/) ) { + split(comparator, a, /\./); + if ( match(comparator, /^([\^~<>]|<=|>=).*$/ ) ) { + comparator=a[1] "." a[2] ".0"; + } else { + comparator=">=" a[1] "." a[2] ".0 <" a[1] "." a[2]+1 ".0" + } + } + + if ( match(comparator, /^\^/) ) { + if ( match(comparator, /^\^0.0.[0-9]+$/) ) { + version=substr(comparator,2); + split(version, a, /\./); + output=output ">=" version " <0.0." a[3]+1 " "; + } else if ( match(comparator, /^\^0.[0-9]+.[0-9]+$/) ) { + version=substr(comparator,2); + split(version, a, /\./); + output=output ">=" version " <0." a[2]+1 ".0 "; + } else if ( match(comparator, /^\^[0-9]+.[0-9]+.[0-9]+$/) ) { + version=substr(comparator,2); + split(version, a, /\./); + output=output ">=" version " <" a[1]+1 ".0.0 "; + } + } else if ( match(comparator, /^~/) ) { + if ( match(comparator, /^~0.0.[0-9]+$/) ) { + version=substr(comparator,2) + split(version, a, /\./) + output=output ">=" version " <0." a[2]+1 ".0 " + } else if ( match(comparator, /^~0.[0-9]+.[0-9]+$/) ) { + version=substr(comparator,2) + split(version, a, /\./) + output=output ">=" version " <0." a[2]+1 ".0 " + } else if ( match(comparator, /^~[0-9]+.[0-9]+.[0-9]+$/) ) { + version=substr(comparator,2) + split(version, a, /\./) + output=output ">=" version " <" a[1] "." a[2]+1 ".0 " + } + } else { + output=output comparator " " + } + } + print output + }' | + + # remove leading/trailing spaces + command sed -E 's/^ +//;s/ +$//' + ) + + # only comparator_sets composed of the required grammar are marked as valid + if nvm_string_contains_regexp "$validated_comparator_set" '^( ?(<|<=|>|>=)?[0-9]+\.[0-9]+\.[0-9]+)+$'; then + validated_semver="$validated_semver || $validated_comparator_set" + else + return 1 + fi + done + + nvm_echo "$(command printf "%s" "$validated_semver" | command sed -E 's/^ \|\| //')" +) } + +nvm_interpret_complex_semver() { ( + [ -n "${1-}" ] || return 1 + + # Validate incoming semver and transform it into the grammar that is expected by the following logic + valid_transformed_semver=$(nvm_validate_semver "${1-}") + [ -n "$valid_transformed_semver" ] || ( nvm_err "invalid semantic version: '${1-}'" && return 1 ) + + # Iterate through the comparator_sets in the semver. + # For each comparator_set, evaluate it to the highest compatible node version. + # Output this function with the highest node version among all the node versions collected in the previous step. + valid_transformed_semver=$(command printf "%s" "$valid_transformed_semver" | command tr '||' '\n') + highest_compatible_node_versions='' + + # TODO add tests verifying this logic correctly gets list of node versions and that the order is from oldest to newest + # list of node versions is sorted from oldest to newest + remote_node_versions=$(nvm_ls_remote | grep -o 'v[0-9]\+\.[0-9]\+\.[0-9]\+') + [ -n "$remote_node_versions" ] || ( nvm_err "failure retrieving remote node versions" && return 1 ) + + while [ -n "$valid_transformed_semver" ]; do + remote_node_versions_copy=$(command printf "%s" "$remote_node_versions") + comparator_set=$(command printf "%s" "$valid_transformed_semver" | head -n1 | command sed -E 's/^ +//;s/ +$//') + valid_transformed_semver=$(command printf "%s" "$valid_transformed_semver" | tail -n +2) + [ -n "$comparator_set" ] || continue + + + # variable indicating the state of the current comparator_set + TRUE=0;FALSE=1 + node_version_compatible=$FALSE + no_remote_version_will_satisfy_all_comparators=$FALSE + + # compare each remote node version (from newest to oldest) to each comparator and stop when you find the newest compatible remote node version + # $node_version_compatible being true indicates that we have found a remote node version that satisfies all the comparators. + # $no_remote_version_will_satisfy_all_comparators being true indicates that there is no remote node version that will satisfy all comparators. + while [ -n "$remote_node_versions_copy" ] && [ $node_version_compatible -eq $FALSE ] && [ $no_remote_version_will_satisfy_all_comparators -eq $FALSE ]; do + # current node version being iterated on + current_remote_node_version=$(command printf "%s" "$remote_node_versions_copy" | tail -n1 | command sed -E 's/^ +//;s/ +$//' | grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+$') + # remove current_remote_node_version from remote_node_versions_copy so that the next iteration will check the next newest node version + remote_node_versions_copy=$(command printf "%s" "$remote_node_versions_copy" | sed '$d') + [ -n "$current_remote_node_version" ] || continue + + node_version_compatible=$FALSE # initialize state + # check if current_remote_node_version is compatible with every comparator in comparator_set. + # stop checking upon finding a comparator that is not satisfied by the current_remote_node_version. + comparator_set_copy=$(command printf "%s" "$comparator_set" | command tr ' ' '\n') + while [ -n "$comparator_set_copy" ]; do + comparator=$(command printf "%s" "$comparator_set_copy" | head -n1 | command sed -E 's/^ +//;s/ +$//') + comparator_set_copy=$(command printf "%s" "$comparator_set_copy" | tail -n +2) + [ -n "$comparator" ] || continue + + # if comparator is satisfied by current_remote_node_version, update state variable node_version_compatible + stripped_version_from_comparator="$(command printf "%s" "$comparator" | grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+$')" + + if nvm_string_contains_regexp "$comparator" '^[0-9]+\.[0-9]+\.[0-9]+$'; then + if [ "$comparator" = "$current_remote_node_version" ]; then + # comparator is looking for an exact match and the current_remote_node_version is that semver so this comparator is satisfied. + node_version_compatible=$TRUE + elif nvm_version_greater "$comparator" "$current_remote_node_version"; then + # looking for a version that is equal to $comparator but $current_remote_node_version is less than $comparator so there is no point continuing. + node_version_compatible=$FALSE + no_remote_version_will_satisfy_all_comparators=$TRUE + else + node_version_compatible=$FALSE + fi + + elif nvm_string_contains_regexp "$comparator" '^<=[0-9]+\.[0-9]+\.[0-9]+$'; then + if nvm_version_greater_than_or_equal_to "$stripped_version_from_comparator" "$current_remote_node_version"; then + # current_remote_node_version is less and or equal to the current comparator version number so this comparator is satisfied. + node_version_compatible=$TRUE + else + node_version_compatible=$FALSE + fi + + elif nvm_string_contains_regexp "$comparator" '^>=[0-9]+\.[0-9]+\.[0-9]+$'; then + if nvm_version_greater_than_or_equal_to "$current_remote_node_version" "$stripped_version_from_comparator"; then + node_version_compatible=$TRUE + else + node_version_compatible=$FALSE + no_remote_version_will_satisfy_all_comparators=$TRUE + fi + + elif nvm_string_contains_regexp "$comparator" '^<[0-9]+\.[0-9]+\.[0-9]+$'; then + if nvm_version_greater "$stripped_version_from_comparator" "$current_remote_node_version"; then + node_version_compatible=$TRUE + else + node_version_compatible=$FALSE + fi + + elif nvm_string_contains_regexp "$comparator" '^>[0-9]+\.[0-9]+\.[0-9]+$'; then + if nvm_version_greater "$current_remote_node_version" "$stripped_version_from_comparator"; then + node_version_compatible=$TRUE + else + node_version_compatible=$FALSE + no_remote_version_will_satisfy_all_comparators=$TRUE + fi + + else + node_version_compatible=$FALSE + fi + # stop checking if all comparators are compatible with current_remote_node_version upon finding one that is incompatible + [ $node_version_compatible -eq $FALSE ] && comparator_set_copy='' + done # while [ -n "$comparator_set_copy" ]; do + # stop iterating through remote_node_versions_copy for this comparator_set upon finding the first remote node version that is compatible with all comparators. + [ $node_version_compatible -eq $TRUE ] && highest_compatible_node_versions="$highest_compatible_node_versions $current_remote_node_version" + done # while [ -n "$remote_node_versions_copy" ] && [ $node_version_compatible -eq $FALSE ] && [ $no_remote_version_will_satisfy_all_comparators -eq $FALSE ]; do + + done + + highest_compatible_node_version='0.0.0' + + highest_compatible_node_versions=$(command printf "%s" "$highest_compatible_node_versions" | command tr ' ' '\n') + while [ -n "$highest_compatible_node_versions" ]; do + compatible_node_version=$(command printf "%s" "$highest_compatible_node_versions" | head -n1 | command sed -E 's/^ +//;s/ +$//') + highest_compatible_node_versions=$(command printf "%s" "$highest_compatible_node_versions" | tail -n +2) + [ -n "$compatible_node_version" ] || continue + + if nvm_version_greater "$compatible_node_version" "$highest_compatible_node_version"; then + highest_compatible_node_version="$compatible_node_version" + fi + done + [ "$highest_compatible_node_version" != '0.0.0' ] && nvm_echo "$highest_compatible_node_version" +) } + +nvm_find_package_json() { ( + dir="$(nvm_find_up 'package.json')" + if [ -e "${dir}/package.json" ]; then + nvm_echo "${dir}/package.json" + fi +) } + +# extracts engines.node value from package.json exactly except: +# - removes all line breaks and carriage returns +# - normalizes all consecutive whitespace to 1 occurrence +# - must match regexp: "[|<> [:alnum:].^=~*-]\+" +nvm_get_node_from_pkg_json() { ( + package_json_contents=${1-} + engines_node_value='' + open_brackets=0 + closed_brackets=0 + in_quotes='false' + + command printf "%s" "$package_json_contents" \ + | command tr -d '\n\r' \ + | command tr '\t' ' ' \ + | command tr -s ' ' \ + | nvm_grep -o '"engines": \?{ \?".*' \ + | nvm_grep -o '{.*' \ + | nvm_grep -o . \ + | while read -r i; do + engines_node_value="$engines_node_value$i" + if [ "$i" = '"' ]; then + if [ "$in_quotes" = 'false' ]; then + in_quotes='true' + else + in_quotes='false' + fi + # spaces are interpretted as '' here but they need to be retained + elif [ "$i" = '' ]; then + engines_node_value="$engines_node_value " + elif [ "$in_quotes" = 'false' ]; then + if [ "$i" = '{' ]; then + open_brackets=$((open_brackets+1)) + elif [ "$i" = '}' ]; then + closed_brackets=$((closed_brackets+1)) + fi + fi + if [ "$open_brackets" -ne 0 ] && [ "$open_brackets" -eq "$closed_brackets" ]; then + command printf "%s" "$engines_node_value" \ + | nvm_grep -o '"node": \?"[|<> [:alnum:].^=~*-]\+"' \ + | command tr -d '"' \ + | command awk -F: '{ print $2 }' \ + | command sed 's/^ //; s/ $//; s/^ //' + return 0 + fi + done + return 2 +) } + +nvm_package_json_version() { + export PKG_JSON_VERSION='' + local pkg_json_path + pkg_json_path="$(nvm_find_package_json)" + if [ ! -e "${pkg_json_path}" ]; then + nvm_err "No package.json file found" + return 1 + fi + PKG_JSON_VERSION=$(nvm_get_node_from_pkg_json "$(command cat "$pkg_json_path")" || command printf '') + if [ ! -n "${PKG_JSON_VERSION}" ]; then + nvm_echo "Warning: could not retrieve engines.node semver expression in package.json file found at \"${pkg_json_path}\"" + return 2 + else + nvm_echo "Found '${pkg_json_path}' with semver expression <${PKG_JSON_VERSION}>" + # attempt complex semver range evaluation + PKG_JSON_VERSION=$(nvm_interpret_complex_semver "$PKG_JSON_VERSION") + if [ ! -n "${PKG_JSON_VERSION}" ]; then + nvm_echo "Warning: could not interpret engines.node semver expression obtained from package.json file." + return 2 + fi + fi +} nvm_find_nvmrc() { local dir @@ -430,6 +781,7 @@ nvm_version() { local PATTERN PATTERN="${1-}" local VERSION + # The default version is the current one if [ -z "${PATTERN}" ]; then PATTERN='current' @@ -2596,14 +2948,19 @@ nvm() { shift fi else - nvm_rc_version - if [ $version_not_provided -eq 1 ] && [ -z "$NVM_RC_VERSION" ]; then + nvm_rc_version || nvm_package_json_version + if [ -n "${NVM_RC_VERSION-}" ]; then + provided_version="$NVM_RC_VERSION" + elif [ -n "${PKG_JSON_VERSION-}" ]; then + provided_version="$PKG_JSON_VERSION" + elif [ $version_not_provided -eq 1 ]; then unset NVM_RC_VERSION + unset PKG_JSON_VERSION >&2 nvm --help return 127 fi - provided_version="$NVM_RC_VERSION" unset NVM_RC_VERSION + unset PKG_JSON_VERSION fi elif [ $# -gt 0 ]; then shift @@ -2947,12 +3304,20 @@ nvm() { if [ -n "${NVM_LTS-}" ]; then VERSION="$(nvm_match_version "lts/${NVM_LTS:-*}")" elif [ -z "${PROVIDED_VERSION-}" ]; then - nvm_rc_version + nvm_rc_version || nvm_package_json_version if [ -n "${NVM_RC_VERSION-}" ]; then PROVIDED_VERSION="$NVM_RC_VERSION" - VERSION="$(nvm_version "$PROVIDED_VERSION")" + elif [ -n "${PKG_JSON_VERSION-}" ]; then + PROVIDED_VERSION="$PKG_JSON_VERSION" + else + unset NVM_RC_VERSION + unset PKG_JSON_VERSION + >&2 nvm --help + return 127 fi + VERSION="$(nvm_version "$PROVIDED_VERSION")" unset NVM_RC_VERSION + unset PKG_JSON_VERSION else VERSION="$(nvm_match_version "$PROVIDED_VERSION")" fi @@ -3543,6 +3908,9 @@ nvm() { nvm_sanitize_path nvm_has_colors nvm_process_parameters \ node_version_has_solaris_binary iojs_version_has_solaris_binary \ nvm_curl_libz_support nvm_command_info \ + nvm_get_node_from_pkg_json nvm_find_package_json nvm_package_json_version \ + nvm_interpret_complex_semver nvm_validate_semver \ + nvm_string_contains_regexp \ > /dev/null 2>&1 unset NVM_RC_VERSION NVM_NODEJS_ORG_MIRROR NVM_IOJS_ORG_MIRROR NVM_DIR \ NVM_CD_FLAGS NVM_BIN NVM_MAKE_JOBS \ diff --git a/semver.md b/semver.md new file mode 100644 index 0000000000..709d4a1da7 --- /dev/null +++ b/semver.md @@ -0,0 +1,50 @@ +# Node semantic version interpretation documentation + +Node versions are dynamically interpretted from the semver expression that is extracted from the local package.json file. The algorithm for doing this is expressed below. Each step is isolated into its own function to make testing and debugging easier. + +## 1. Extract the semver expression located in the engines.node value of the local package.json file. + +#### Required input grammar for semver expression extracted from package.json: + +> Grammar is copied from https://docs.npmjs.com/misc/semver +> ``` +> range-set ::= range ( logical-or range ) * +> logical-or ::= ( ' ' ) * '||' ( ' ' ) * +> range ::= hyphen | simple ( ' ' simple ) * | '' +> hyphen ::= partial ' - ' partial +> simple ::= primitive | partial | tilde | caret +> primitive ::= ( '<' | '>' | '>=' | '<=' | '=' | ) partial +> partial ::= xr ( '.' xr ( '.' xr qualifier ? )? )? +> xr ::= 'x' | 'X' | '*' | nr +> nr ::= '0' | ['1'-'9'] ( ['0'-'9'] ) * +> tilde ::= '~' partial +> caret ::= '^' partial +> qualifier ::= ( '-' pre )? ( '+' build )? +> pre ::= parts +> build ::= parts +> parts ::= part ( '.' part ) * +> part ::= nr | [-0-9A-Za-z]+ +> ``` +> Lazy grammar validation is used at this point. Basically any string in the engines.node value will be accepted at this point that matches the following regexp: +> "[|<> [:alnum:].^=~*-]\+" +> +> NOTE: all whitespace inside the engines.node value is normalized to be a single space in this step. + +## 2. Check that the extracted semver expression from the previous step matches the above grammar. If so, normalize it into the following grammar that is expected by the interpretation logic. + +#### Required input grammar for internal interpretation logic: + +> ``` +> semver ::= comparator_set ( ' || ' comparator_set )* +> comparator_set ::= comparator ( ' ' comparator )* +> comparator ::= ( '<' | '<=' | '>' | '>=' | '' ) [0-9]+ '.' [0-9]+ '.' [0-9]+ +> ``` + +## 3. Interpret the normalized semver expression. + +> 1. Resolve each comparator set to the newest compatible node version +> - iterate through node versions from newest to oldest +> - find the first node version that satisfies all comparators +> - if reached a point where no older node versions will satisfy comparator, stop iterating through node versions. +> 2. Choose the newest node version among the resolved node versions from the previous step. + diff --git a/test/fast/Unit tests/nvm_get_node_from_pkg_json b/test/fast/Unit tests/nvm_get_node_from_pkg_json new file mode 100755 index 0000000000..25526cbcad --- /dev/null +++ b/test/fast/Unit tests/nvm_get_node_from_pkg_json @@ -0,0 +1,242 @@ +#!/bin/sh + +die () { printf "$@" ; exit 1; } + +\. ../../../nvm.sh + +BASIC_VERSIONS='1.2.3 +10.21.32 +x.x.x +X.X.X +*.*.* +1.2 +10.21 +x.x +X.X +*.* +1 +10 +x +* +X' + +SEMVER_OPERATORS='v += +< +> +<= +>= +~ +^' + + + +# Dynamically generate list of semver expressions to test inside each package.json template. +# Each semver added to this list is paired with the expected output when executed with nvm_get_node_from_pkg_json. +# Most valid semvers will expecte the output to be the same as the input but there are some valid semvers with certain +# sequences of tabs/spaces whose outputs are slightly different from their inputs (documented below). + +# Format for test cases is input and expected output separated by a colon +# INPUT:EXPECTED_OUTPUT +TEST_SEMVERS='' + +BASIC_VERSIONS_COPY=$(echo "$BASIC_VERSIONS") +while [ -n "$BASIC_VERSIONS_COPY" ]; do + BASIC_VERSION=$(echo "$BASIC_VERSIONS_COPY" | head -n1) + BASIC_VERSIONS_COPY=$(echo "$BASIC_VERSIONS_COPY" | tail -n +2) + + # add test semver with just the version and no comparator + TEST_SEMVERS="$TEST_SEMVERS +$BASIC_VERSION:$BASIC_VERSION" + + SEMVER_OPERATORS_COPY=$(echo "$SEMVER_OPERATORS") + while [ -n "$SEMVER_OPERATORS_COPY" ]; do + SEMVER_OPERATOR=$(echo "$SEMVER_OPERATORS_COPY" | head -n1) + SEMVER_OPERATORS_COPY=$(echo "$SEMVER_OPERATORS_COPY" | tail -n +2) + + # add test semver with version adjacent to operator + # add test semver with version separated from operator by one space + TEST_SEMVERS="$TEST_SEMVERS +$SEMVER_OPERATOR$BASIC_VERSION:$SEMVER_OPERATOR$BASIC_VERSION +$SEMVER_OPERATOR $BASIC_VERSION:$SEMVER_OPERATOR $BASIC_VERSION" + done +done + +# add valid basic test semvers with hyphen ranges +TEST_SEMVERS="$TEST_SEMVERS +1.2.3 - 1.2.4:1.2.3 - 1.2.4 +10.21.32 - 10.21.33:10.21.32 - 10.21.33 +1.2 - 1.3:1.2 - 1.3 +10.21 - 10.22:10.21 - 10.22 +1 - 2:1 - 2 +10 - 11:10 - 11" + +# add more complex test semvers with just one comparator set +TEST_SEMVERS="$TEST_SEMVERS +1.2.3 1.2.4:1.2.3 1.2.4 +1.2 1.3:1.2 1.3 +1 2:1 2 +>1.2.3 <=1.3.0:>1.2.3 <=1.3.0" + +# add test semvers with multiple comparator sets +TEST_SEMVERS="$TEST_SEMVERS +1.2.3 || 1.2.4:1.2.3 || 1.2.4 +1.2 || 1.3:1.2 || 1.3 +1 || 2:1 || 2" + +# add test semvers that will be successfully extracted from package.json +# but will be marked invalid upon going through nvm_validate_semver +TEST_SEMVERS="$TEST_SEMVERS +1.2.3||1.2.4:1.2.3||1.2.4 +1.2||1.3:1.2||1.3 +1||2:1||2 +<1.2.3>:<1.2.3> +<1.2>:<1.2> +<1>:<1> +>>1:>>1 +<<1:<<1 +==1:==1 +**:** +xx:xx +^^1:^^1 +~~1:~~1 +1.2.3-1.2.4:1.2.3-1.2.4 +10.21.32-10.21.33:10.21.32-10.21.33 +1.2-1.3:1.2-1.3 +10.21-10.22:10.21-10.22 +1-2:1-2 +10-11:10-11" + +# add test semvers with tabs inside them +# These semvers are intended to: +# - validate that semvers can include tabs +# - validate that all tabs or consecutive tabs are reduced to one space +# - validate that leading trailing spaces and tabs are removed +TEST_SEMVERS="$TEST_SEMVERS + 1 1 :1 1 + 1 1 :1 1" + +# remove first line which is just an empty line +TEST_SEMVERS=$(echo "$TEST_SEMVERS" | tail -n +2) + + +# POSITIVE TEST CASES + + +# (TEST SET #1) uses valid TEST_SEMVER's and valid package.json templates +TEST_SEMVERS_COPY=$(echo "$TEST_SEMVERS") +PREV_TEST_SEMVER='' +for TEMPLATE_NAME in package_json_templates/_valid_*; do + while [ -n "$TEST_SEMVERS_COPY" ]; do + LINE=$(echo "$TEST_SEMVERS_COPY" | head -n1) + TEST_SEMVER_INPUT=$(echo "$LINE" | awk -F: '{ print $1 }') + EXPECTED_OUTPUT=$(echo "$LINE" | awk -F: '{ print $2 }') + TEST_SEMVERS_COPY=$(echo "$TEST_SEMVERS_COPY" | tail -n +2) + + [ "$PREV_TEST_SEMVER" = "$TEST_SEMVER_INPUT" ] \ + && die "Problem iterating through TEST_SEMVERS_COPY (TEST SET #1). Encountered the same value twice in a row. PREV_TEST_SEMVER='$PREV_TEST_SEMVER' TEST_SEMVER_INPUT='$TEST_SEMVER_INPUT'.\n" + PREV_TEST_SEMVER=$(printf "%s" "$TEST_SEMVER_INPUT") + + PKG_JSON_CONTENTS=$(sed 's/NODE_SEMVER/'"$TEST_SEMVER_INPUT"'/g' "$TEMPLATE_NAME" | tail -n +3) + ACTUAL_OUTPUT=$(nvm_get_node_from_pkg_json "$PKG_JSON_CONTENTS") + + [ "$ACTUAL_OUTPUT" = "$EXPECTED_OUTPUT" ] \ + && [ -n "$ACTUAL_OUTPUT" ] \ + && [ -n "$PKG_JSON_CONTENTS" ] \ + || die "'nvm_get_node_from_pkg_json' POSITIVE test case failed (TEST SET #1). + Expected '$EXPECTED_OUTPUT' but got '$ACTUAL_OUTPUT' when given input template '$TEMPLATE_NAME':\n$PKG_JSON_CONTENTS" + done +done + + +# NEGATIVE TEST CASES + + +# (TEST SET #2) uses valid TEST_SEMVER's but invalid package.json templates +TEST_SEMVERS_COPY=$(echo "$TEST_SEMVERS") +PREV_TEST_SEMVER='' +for TEMPLATE_NAME in package_json_templates/_invalid_*; do + while [ -n "$TEST_SEMVERS_COPY" ]; do + LINE=$(echo "$TEST_SEMVERS_COPY" | head -n1) + TEST_SEMVER_INPUT=$(echo "$LINE" | awk -F: '{ print $1 }') + TEST_SEMVERS_COPY=$(echo "$TEST_SEMVERS_COPY" | tail -n +2) + + [ "$PREV_TEST_SEMVER" = "$TEST_SEMVER_INPUT" ] \ + && die "Problem iterating through TEST_SEMVERS_COPY (TEST SET #2). Encountered the same value twice in a row. PREV_TEST_SEMVER='$PREV_TEST_SEMVER' TEST_SEMVER_INPUT='$TEST_SEMVER_INPUT'.\n" + PREV_TEST_SEMVER=$(printf "%s" "$TEST_SEMVER_INPUT") + + PKG_JSON_CONTENTS=$(sed 's/NODE_SEMVER/'"$TEST_SEMVER_INPUT"'/g' "$TEMPLATE_NAME" | tail -n +3) + ACTUAL_OUTPUT=$(nvm_get_node_from_pkg_json "$PKG_JSON_CONTENTS") + + [ "$ACTUAL_OUTPUT" = "" ] \ + && [ -n "$TEST_SEMVER_INPUT" ] \ + && [ -n "$PKG_JSON_CONTENTS" ] \ + || die "'nvm_get_node_from_pkg_json' NEGATIVE test case failed (TEST SET #2). + Expected to get empty string but got '$ACTUAL_OUTPUT' when given input template '$TEMPLATE_NAME':\n$PKG_JSON_CONTENTS" + done +done + + +# invalid test semvers +TEST_SEMVERS='&1 +@1 +#1 +$1 +%s +1) +1( +1_ +1+ +1] +1[ +1" +1: +1? +1` +1!' + +# (TEST SET #3) uses invalid TEST_SEMVER's but valid package.json templates +TEST_SEMVERS_COPY=$(echo "$TEST_SEMVERS") +PREV_TEST_SEMVER='' +for TEMPLATE_NAME in package_json_templates/_valid_*; do + while [ -n "$TEST_SEMVERS_COPY" ]; do + TEST_SEMVER_INPUT=$(echo "$TEST_SEMVERS_COPY" | head -n1) + TEST_SEMVERS_COPY=$(echo "$TEST_SEMVERS_COPY" | tail -n +2) + + [ "$PREV_TEST_SEMVER" = "$TEST_SEMVER_INPUT" ] \ + && die "Problem iterating through TEST_SEMVERS_COPY (TEST SET #3). Encountered the same value twice in a row. PREV_TEST_SEMVER='$PREV_TEST_SEMVER' TEST_SEMVER_INPUT='$TEST_SEMVER_INPUT'.\n" + PREV_TEST_SEMVER=$(printf "%s" "$TEST_SEMVER_INPUT") + + PKG_JSON_CONTENTS=$(sed 's/NODE_SEMVER/'"$TEST_SEMVER_INPUT"'/g' "$TEMPLATE_NAME" | tail -n +3) + ACTUAL_OUTPUT=$(nvm_get_node_from_pkg_json "$PKG_JSON_CONTENTS") + + [ "$ACTUAL_OUTPUT" = "" ] \ + && [ -n "$TEST_SEMVER_INPUT" ] \ + && [ -n "$PKG_JSON_CONTENTS" ] \ + || die "'nvm_get_node_from_pkg_json' NEGATIVE test case failed (TEST SET #3). + Expected to get empty string but got '$ACTUAL_OUTPUT' when given input template '$TEMPLATE_NAME':\n$PKG_JSON_CONTENTS" + done +done + +# (TEST SET #4) uses invalid TEST_SEMVER's and invalid package.json templates +TEST_SEMVERS_COPY=$(echo "$TEST_SEMVERS") +PREV_TEST_SEMVER='' +for TEMPLATE_NAME in package_json_templates/_invalid_*; do + while [ -n "$TEST_SEMVERS_COPY" ]; do + TEST_SEMVER_INPUT=$(echo "$TEST_SEMVERS_COPY" | head -n1) + TEST_SEMVERS_COPY=$(echo "$TEST_SEMVERS_COPY" | tail -n +2) + + [ "$PREV_TEST_SEMVER" = "$TEST_SEMVER_INPUT" ] \ + && die "Problem iterating through TEST_SEMVERS_COPY (TEST SET #4). Encountered the same value twice in a row. PREV_TEST_SEMVER='$PREV_TEST_SEMVER' TEST_SEMVER_INPUT='$TEST_SEMVER_INPUT'.\n" + PREV_TEST_SEMVER=$(printf "%s" "$TEST_SEMVER_INPUT") + + PKG_JSON_CONTENTS=$(sed 's/NODE_SEMVER/'"$TEST_SEMVER_INPUT"'/g' "$TEMPLATE_NAME" | tail -n +3) + ACTUAL_OUTPUT=$(nvm_get_node_from_pkg_json "$PKG_JSON_CONTENTS") + + [ "$ACTUAL_OUTPUT" = "" ] \ + && [ -n "$TEST_SEMVER_INPUT" ] \ + && [ -n "$PKG_JSON_CONTENTS" ] \ + || die "'nvm_get_node_from_pkg_json' NEGATIVE test case failed (TEST SET #4). + Expected to get empty string but got '$ACTUAL_OUTPUT' when given input template '$TEMPLATE_NAME':\n$PKG_JSON_CONTENTS" + done +done diff --git a/test/fast/Unit tests/nvm_interpret_complex_semver b/test/fast/Unit tests/nvm_interpret_complex_semver new file mode 100755 index 0000000000..ec06286e7e --- /dev/null +++ b/test/fast/Unit tests/nvm_interpret_complex_semver @@ -0,0 +1,36 @@ +#!/bin/sh + +die () { printf "$@" ; exit 1; } + +\. ../../../nvm.sh + +newest_node=$(nvm_remote_version | grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+') +x5='5.12.0' + +# POSITIVE TEST CASES + +# INPUT:EXPECTED_OUTPUT +test_cases="*:$newest_node +5:$x5 +x:$newest_node +X:$newest_node +7.1.0 || 7.3.0 || 7.2.0:7.3.0 +7.1.0 7.3.0 7.2.0: +5:$x5 +5.x:$x5 +5.x.x:$x5 +5.X:$x5 +5.X.X:$x5 +7.5.0:7.5.0" + +while [ -n "$test_cases" ]; do + LINE=$(echo "$test_cases" | head -n1) + INPUT=$(echo "$LINE" | awk -F: '{ print $1 }') + EXPECTED_OUTPUT=$(echo "$LINE" | awk -F: '{ print $2 }') + ACTUAL_OUTPUT=$(nvm_interpret_complex_semver "$INPUT") + [ "$ACTUAL_OUTPUT" = "$EXPECTED_OUTPUT" ] \ + && [ -n "$INPUT" ] \ + || die "Expected output: '$EXPECTED_OUTPUT'. Actual output: '$ACTUAL_OUTPUT'. Input: '$INPUT'.\n" + test_cases=$(echo "$test_cases" | tail -n +2) +done +exit 0 diff --git a/test/fast/Unit tests/nvm_string_equals_regexp b/test/fast/Unit tests/nvm_string_equals_regexp new file mode 100755 index 0000000000..3ca56378ae --- /dev/null +++ b/test/fast/Unit tests/nvm_string_equals_regexp @@ -0,0 +1,42 @@ +#!/bin/sh + +die () { printf "$@" ; exit 1; } + +\. ../../../nvm.sh + +valid_semver_regexp='^( ?(<|<=|>|>=|=|~|\^)?([0-9]+|x)\.([0-9]+|x)\.([0-9]+|x))+$' + +# POSITIVE TEST CASES + +# STRING:REGEXP +test_cases="1.2.3:$valid_semver_regexp +11.22.33:$valid_semver_regexp" + +while [ -n "$test_cases" ]; do + LINE=$(echo "$test_cases" | head -n1) + STRING=$(echo "$LINE" | awk -F: '{ print $1 }') + REGEXP=$(echo "$LINE" | awk -F: '{ print $2 }') + [ -n "$REGEXP" ] \ + && [ -n "$STRING" ] \ + && nvm_string_contains_regexp "$STRING" "$REGEXP" \ + || die "nvm_string_contains_regexp POSITIVE test case failed. REGEXP: '$REGEXP'. STRING: '$STRING'.\n" + test_cases=$(echo "$test_cases" | tail -n +2) +done + +# NEGATIVE TEST CASES + +# STRING:REGEXP +test_cases="1.2.a:$valid_semver_regexp +:$valid_semver_regexp +11.22.a:$valid_semver_regexp" + +while [ -n "$test_cases" ]; do + LINE=$(echo "$test_cases" | head -n1) + STRING=$(echo "$LINE" | awk -F: '{ print $1 }') + REGEXP=$(echo "$LINE" | awk -F: '{ print $2 }') + [ -n "$REGEXP" ] \ + && ! nvm_string_contains_regexp "$STRING" "$REGEXP" \ + || die "nvm_string_contains_regexp NEGATIVE test case failed. REGEXP: '$REGEXP'. STRING: '$STRING'.\n" + test_cases=$(echo "$test_cases" | tail -n +2) +done +exit 0 diff --git a/test/fast/Unit tests/nvm_validate_semver b/test/fast/Unit tests/nvm_validate_semver new file mode 100755 index 0000000000..ec00041b97 --- /dev/null +++ b/test/fast/Unit tests/nvm_validate_semver @@ -0,0 +1,54 @@ +#!/bin/sh + +die () { printf "$@" ; exit 1; } + +\. ../../../nvm.sh + +# INPUT:EXPECTED_OUTPUT +# TODO refactor test cases into clearer structure +test_cases="1.2.3:1.2.3 +1.2.3 - 1.2.4:>=1.2.3 <=1.2.4 +1.2.3-1.2.4:>=1.2.3 <=1.2.4 +11.22.33 - 11.22.44:>=11.22.33 <=11.22.44 +1.2.x - 1.2.4:>=1.2.0 <=1.2.4 +1.2.xx - 1.2.4: +1.2.3 || 1.2.4:1.2.3 || 1.2.4 +*:>0.0.0 +x:>0.0.0 +X:>0.0.0 +1:>=1.0.0 <2.0.0 +1.2:>=1.2.0 <1.3.0 +< 1.2.3:<1.2.3 +> 1.2.3:>1.2.3 +<= 1.2.3:<=1.2.3 +>= 1.2.3:>=1.2.3 += 1.2.3:1.2.3 +^0.0.1 ^0.1.2 ^1.2.3:>=0.0.1 <0.0.2 >=0.1.2 <0.2.0 >=1.2.3 <2.0.0 +~0.0.1 ~0.1.2 ~1.2.3:>=0.0.1 <0.1.0 >=0.1.2 <0.2.0 >=1.2.3 <1.3.0 +~ 1.2.3:>=1.2.3 <1.3.0 +^ 1.2.3:>=1.2.3 <2.0.0 +1.2.3 || 1.2.4 1.2.5:1.2.3 || 1.2.4 1.2.5 +a: +1 || 2 a: +1 || a: +a || 1.2.3: +1.2.?: +^0.0.1:>=0.0.1 <0.0.2 +=x.1.1:>0.0.0 +>x.1.1:>0.0.0 +~x.1.1:>0.0.0 +^x.1.1:>0.0.0 +11.22.33:11.22.33" + +while [ -n "$test_cases" ]; do + LINE=$(echo "$test_cases" | head -n1) + INPUT=$(echo "$LINE" | awk -F: '{ print $1 }') + EXPECTED_OUTPUT=$(echo "$LINE" | awk -F: '{ print $2 }') + ACTUAL_OUTPUT=$(nvm_validate_semver "$INPUT") + [ "$ACTUAL_OUTPUT" = "$EXPECTED_OUTPUT" ] \ + && [ -n "$INPUT" ] \ + || die "Expected output: '$EXPECTED_OUTPUT'. Actual output: '$ACTUAL_OUTPUT'. Input: '$INPUT'.\n" + test_cases=$(echo "$test_cases" | tail -n +2) +done +exit 0 diff --git a/test/fast/Unit tests/package_json_templates/_invalid_missing_engines b/test/fast/Unit tests/package_json_templates/_invalid_missing_engines new file mode 100644 index 0000000000..093b79525c --- /dev/null +++ b/test/fast/Unit tests/package_json_templates/_invalid_missing_engines @@ -0,0 +1,6 @@ +verifies incompatibility with package.json that is missing 'engines' key + +{ + "name": "fake", + "description": "fake" +} diff --git a/test/fast/Unit tests/package_json_templates/_invalid_missing_node b/test/fast/Unit tests/package_json_templates/_invalid_missing_node new file mode 100644 index 0000000000..f3a692247b --- /dev/null +++ b/test/fast/Unit tests/package_json_templates/_invalid_missing_node @@ -0,0 +1,9 @@ +verifies incompatibility with package.json that is missing 'engines.node' key + +{ + "name": "fake", + "engines": { + "npm": "fake" + }, + "description": "fake" +} diff --git a/test/fast/Unit tests/package_json_templates/_invalid_missing_quotes b/test/fast/Unit tests/package_json_templates/_invalid_missing_quotes new file mode 100644 index 0000000000..531b5ab831 --- /dev/null +++ b/test/fast/Unit tests/package_json_templates/_invalid_missing_quotes @@ -0,0 +1,9 @@ +verifies incompatibility with package.json that is missing quotes around the 'engines'node' value + +{ + "name": "fake", + "engines": { + "node": NODE_SEMVER + }, + "description": "fake" +} diff --git a/test/fast/Unit tests/package_json_templates/_valid_with_extra_bracket b/test/fast/Unit tests/package_json_templates/_valid_with_extra_bracket new file mode 100644 index 0000000000..6c933c78ab --- /dev/null +++ b/test/fast/Unit tests/package_json_templates/_valid_with_extra_bracket @@ -0,0 +1,9 @@ +verifies compatibility with basic package.json with keys in the 'engines' object that have brackets + +{ + "name": "fake", + "engines": { + "potentialFutureKeyWithBracket}": "fake", + "node": "NODE_SEMVER" + } +} diff --git a/test/fast/Unit tests/package_json_templates/_valid_with_spaces b/test/fast/Unit tests/package_json_templates/_valid_with_spaces new file mode 100644 index 0000000000..8f273bb10a --- /dev/null +++ b/test/fast/Unit tests/package_json_templates/_valid_with_spaces @@ -0,0 +1,9 @@ +verifies compatibility with basic package.json indented with spaces + +{ + "name": "fake", + "engines": { + "node": "NODE_SEMVER" + }, + "description": "fake" +} diff --git a/test/fast/Unit tests/package_json_templates/_valid_with_tabs b/test/fast/Unit tests/package_json_templates/_valid_with_tabs new file mode 100644 index 0000000000..ccca3371e0 --- /dev/null +++ b/test/fast/Unit tests/package_json_templates/_valid_with_tabs @@ -0,0 +1,9 @@ +verifies compatibility with basic package.json indented with tabs + +{ + "name": "fake", + "engines": { + "node": "NODE_SEMVER" + }, + "description": "fake" +} diff --git a/test/fast/Unit tests/package_json_templates/_valid_with_tabs_in_semver b/test/fast/Unit tests/package_json_templates/_valid_with_tabs_in_semver new file mode 100644 index 0000000000..1b1c73c1f6 --- /dev/null +++ b/test/fast/Unit tests/package_json_templates/_valid_with_tabs_in_semver @@ -0,0 +1,9 @@ +verifies compatibility with basic package.json with tabs in the 'engines.node' value + +{ + "name": "fake", + "engines": { + "node": " NODE_SEMVER " + }, + "description": "fake" +} From d4dee0c6a370578e4ce707f5ade7b84d868a78f8 Mon Sep 17 00:00:00 2001 From: edwmurph Date: Sun, 27 May 2018 17:11:08 -0400 Subject: [PATCH 02/12] chnages from review; added optimization for common simple semvers --- nvm.sh | 397 ++++++++++++------ ...et_complex_semver => nvm_interpret_semver} | 4 +- 2 files changed, 267 insertions(+), 134 deletions(-) rename test/fast/Unit tests/{nvm_interpret_complex_semver => nvm_interpret_semver} (77%) diff --git a/nvm.sh b/nvm.sh index d590c9fa27..8d032c1223 100644 --- a/nvm.sh +++ b/nvm.sh @@ -299,30 +299,47 @@ nvm_find_up() { nvm_echo "${path_}" } -nvm_string_contains_regexp() { - local string +# NOTE: Surrounding the function body in parens limits the scope of variables in this function to only this function. +nvm_string_contains_regexp() { ( string="${1-}" - local regexp regexp="${2-}" - if [ -z "${string-}" ] || [ -z "${regexp-}" ]; then + if [ -z "$string" ] || [ -z "$regexp" ]; then return 1 fi # e.g. "nvm_string_contains_regexp abbc ^aa?b+.$" returns 0 command printf "%s" "$string" | command awk "/$regexp/{ exit 0 }{ exit 1 }" +) } + +# Validates that the given semver adheres to the following grammar: +# +# semver ::= comparator_set ( ' || ' comparator_set )* +# comparator_set ::= comparator ( ' ' comparator )* +# comparator ::= ( '<' | '<=' | '>' | '>=' | '' ) [0-9]+ '.' [0-9]+ '.' [0-9]+ +nvm_is_valid_semver() { + if nvm_string_contains_regexp "${1-}" '^( ?(<|<=|>|>=)?[0-9]+\.[0-9]+\.[0-9]+)+( \|\| ( ?(<|<=|>|>=)?[0-9]+\.[0-9]+\.[0-9]+)+)*$'; then + return 0 + else + return 1 + fi } -# scoped function returns nothing or a normalized semver adhering to the following grammar: +# Attempts to convert given semver to the following grammar: # # semver ::= comparator_set ( ' || ' comparator_set )* # comparator_set ::= comparator ( ' ' comparator )* # comparator ::= ( '<' | '<=' | '>' | '>=' | '' ) [0-9]+ '.' [0-9]+ '.' [0-9]+ +# +# NOTE: Surrounding the function body in parens limits the scope of variables in this function to only this function. nvm_validate_semver() { ( + # split the semantic version into comparator_set's semver=$(command printf "%s" "${1-}" | command tr '||' '\n') - [ -n "$semver" ] || return 1 + if [ -z "$semver" ]; then + return 1 + fi validated_semver=''; while [ -n "$semver" ]; do - comparator_set=$(command printf "%s" "$semver" | head -n1) - semver=$(command printf "%s" "$semver" | tail -n +2) + comparator_set=$(command printf "%s" "$semver" | command head -n1) + semver=$(command printf "%s" "$semver" | command tail -n +2) [ -n "$comparator_set" ] || continue # convert comparators into required grammar @@ -357,9 +374,6 @@ nvm_validate_semver() { ( # ` =1.2.3 ` => ` 1.2.3 ` command sed -E 's/ =//g' | - # trim leading/trailing spaces - command sed -E 's/^ //;s/ $//' | - # handle conversions of comparators with '^' or '~' or 'x' into required grammar # ` ^0.0.1 ` => ` >=0.0.1 <0.0.2 ` # ` ^0.1.2 ` => ` >=0.1.2 <0.2.0 ` @@ -448,148 +462,264 @@ nvm_validate_semver() { ( fi done - nvm_echo "$(command printf "%s" "$validated_semver" | command sed -E 's/^ \|\| //')" + validated_semver=$(command printf "%s" "$validated_semver" | command sed -E 's/^ \|\| //') + + if nvm_is_valid_semver "$validated_semver"; then + command printf "%s" "$validated_semver" + else + return 1 + fi ) } +# Given a semver and version list, find the highest compatible version by doing the following: +# - Find the newest compatible version of each comparator set. +# - Resolve to the newest of all the newest compatible versions of each comparator set. +# NOTE: Surrounding the function body in parens limits the scope of variables in this function to only this function. nvm_interpret_complex_semver() { ( - [ -n "${1-}" ] || return 1 - - # Validate incoming semver and transform it into the grammar that is expected by the following logic - valid_transformed_semver=$(nvm_validate_semver "${1-}") - [ -n "$valid_transformed_semver" ] || ( nvm_err "invalid semantic version: '${1-}'" && return 1 ) - - # Iterate through the comparator_sets in the semver. - # For each comparator_set, evaluate it to the highest compatible node version. - # Output this function with the highest node version among all the node versions collected in the previous step. - valid_transformed_semver=$(command printf "%s" "$valid_transformed_semver" | command tr '||' '\n') - highest_compatible_node_versions='' - - # TODO add tests verifying this logic correctly gets list of node versions and that the order is from oldest to newest - # list of node versions is sorted from oldest to newest - remote_node_versions=$(nvm_ls_remote | grep -o 'v[0-9]\+\.[0-9]\+\.[0-9]\+') - [ -n "$remote_node_versions" ] || ( nvm_err "failure retrieving remote node versions" && return 1 ) - - while [ -n "$valid_transformed_semver" ]; do - remote_node_versions_copy=$(command printf "%s" "$remote_node_versions") - comparator_set=$(command printf "%s" "$valid_transformed_semver" | head -n1 | command sed -E 's/^ +//;s/ +$//') - valid_transformed_semver=$(command printf "%s" "$valid_transformed_semver" | tail -n +2) - [ -n "$comparator_set" ] || continue + semver="${1-}" + version_list="${2-}" # expected to be sorted from oldest to newest + if [ -z "$semver" ] || [ -z "$version_list" ]; then + nvm_err "Error interpretting complex semver: Missing required parameter(s)" + return 1 + elif ! nvm_is_valid_semver "$semver"; then + nvm_err "Error interpretting complex semver: Given an invalid semver" + return 1 + fi + # For each comparator_set in the semver: + # - Resolve the comparator_set to its newest compatible version. + # - Add the discovered newest compatible version to highest_compatible_versions. + # - Choose the highest version among all the versions collected in highest_compatible_versions. + semver=$(command printf "%s" "$semver" | command tr '||' '\n') + highest_compatible_versions='' - # variable indicating the state of the current comparator_set - TRUE=0;FALSE=1 - node_version_compatible=$FALSE - no_remote_version_will_satisfy_all_comparators=$FALSE - - # compare each remote node version (from newest to oldest) to each comparator and stop when you find the newest compatible remote node version - # $node_version_compatible being true indicates that we have found a remote node version that satisfies all the comparators. - # $no_remote_version_will_satisfy_all_comparators being true indicates that there is no remote node version that will satisfy all comparators. - while [ -n "$remote_node_versions_copy" ] && [ $node_version_compatible -eq $FALSE ] && [ $no_remote_version_will_satisfy_all_comparators -eq $FALSE ]; do - # current node version being iterated on - current_remote_node_version=$(command printf "%s" "$remote_node_versions_copy" | tail -n1 | command sed -E 's/^ +//;s/ +$//' | grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+$') - # remove current_remote_node_version from remote_node_versions_copy so that the next iteration will check the next newest node version - remote_node_versions_copy=$(command printf "%s" "$remote_node_versions_copy" | sed '$d') - [ -n "$current_remote_node_version" ] || continue - - node_version_compatible=$FALSE # initialize state - # check if current_remote_node_version is compatible with every comparator in comparator_set. - # stop checking upon finding a comparator that is not satisfied by the current_remote_node_version. - comparator_set_copy=$(command printf "%s" "$comparator_set" | command tr ' ' '\n') - while [ -n "$comparator_set_copy" ]; do - comparator=$(command printf "%s" "$comparator_set_copy" | head -n1 | command sed -E 's/^ +//;s/ +$//') - comparator_set_copy=$(command printf "%s" "$comparator_set_copy" | tail -n +2) - [ -n "$comparator" ] || continue - - # if comparator is satisfied by current_remote_node_version, update state variable node_version_compatible - stripped_version_from_comparator="$(command printf "%s" "$comparator" | grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+$')" - - if nvm_string_contains_regexp "$comparator" '^[0-9]+\.[0-9]+\.[0-9]+$'; then - if [ "$comparator" = "$current_remote_node_version" ]; then - # comparator is looking for an exact match and the current_remote_node_version is that semver so this comparator is satisfied. - node_version_compatible=$TRUE - elif nvm_version_greater "$comparator" "$current_remote_node_version"; then - # looking for a version that is equal to $comparator but $current_remote_node_version is less than $comparator so there is no point continuing. - node_version_compatible=$FALSE - no_remote_version_will_satisfy_all_comparators=$TRUE + while [ -n "$semver" ]; do + version_list_copy=$(command printf "%s" "$version_list") + current_comparator_set=$(command printf "%s" "$semver" | command head -n1 | command sed -E 's/^ +//;s/ +$//') + semver=$(command printf "%s" "$semver" | command tail -n +2) + [ -n "$current_comparator_set" ] || continue + + is_current_version_compatible=1 # initialized to false + + # For each version in the version_list_copy (from newest to oldest): + # - If current_version satisfies all comparators, we've found the newest version compatible with all comparators in current current_comparator_set. + # - Add discovered version to highest_compatible_versions and stop iterating through versions. + while [ -n "$version_list_copy" ] && [ $is_current_version_compatible -eq 1 ]; do + current_version=$(command printf "%s" "$version_list_copy" | command tail -n1 | command sed -E 's/^ +//;s/ +$//' | nvm_grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+$') + version_list_copy=$(command printf "%s" "$version_list_copy" | command sed '$d') + [ -n "$current_version" ] || continue + + is_current_version_compatible=1 # initialized to false + + # For each comparator in the current_comparator_set_copy: + # - If current_version is compatible with all comparators, we know current_version is the newest compatible version + current_comparator_set_copy=$(command printf "%s" "$current_comparator_set" | command tr ' ' '\n') + while [ -n "$current_comparator_set_copy" ]; do + current_comparator=$(command printf "%s" "$current_comparator_set_copy" | command head -n1 | command sed -E 's/^ +//;s/ +$//') + current_comparator_set_copy=$(command printf "%s" "$current_comparator_set_copy" | command tail -n +2) + [ -n "$current_comparator" ] || continue + + stripped_version_from_comparator="$(command printf "%s" "$current_comparator" | nvm_grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+$')" + + if nvm_string_contains_regexp "$current_comparator" '^[0-9]+\.[0-9]+\.[0-9]+$'; then + if [ "$current_comparator" = "$current_version" ]; then + # current_omparator is looking for an exact match, and the current_version is the exact match, so this current_comparator is satisfied. + is_current_version_compatible=0 + elif nvm_version_greater "$current_comparator" "$current_version"; then + # Looking for a version that is equal to current_comparator but current_version is less than current_comparator so there is no point continuing. + is_current_version_compatible=1 + no_remote_version_will_satisfy_all_comparators=0 else - node_version_compatible=$FALSE + is_current_version_compatible=1 fi - elif nvm_string_contains_regexp "$comparator" '^<=[0-9]+\.[0-9]+\.[0-9]+$'; then - if nvm_version_greater_than_or_equal_to "$stripped_version_from_comparator" "$current_remote_node_version"; then - # current_remote_node_version is less and or equal to the current comparator version number so this comparator is satisfied. - node_version_compatible=$TRUE + elif nvm_string_contains_regexp "$current_comparator" '^<=[0-9]+\.[0-9]+\.[0-9]+$'; then + if nvm_version_greater_than_or_equal_to "$stripped_version_from_comparator" "$current_version"; then + # current_version is less and or equal to the current_comparator version number so this current_comparator is satisfied. + is_current_version_compatible=0 else - node_version_compatible=$FALSE + is_current_version_compatible=1 fi - elif nvm_string_contains_regexp "$comparator" '^>=[0-9]+\.[0-9]+\.[0-9]+$'; then - if nvm_version_greater_than_or_equal_to "$current_remote_node_version" "$stripped_version_from_comparator"; then - node_version_compatible=$TRUE + elif nvm_string_contains_regexp "$current_comparator" '^>=[0-9]+\.[0-9]+\.[0-9]+$'; then + if nvm_version_greater_than_or_equal_to "$current_version" "$stripped_version_from_comparator"; then + is_current_version_compatible=0 else - node_version_compatible=$FALSE - no_remote_version_will_satisfy_all_comparators=$TRUE + is_current_version_compatible=1 + no_remote_version_will_satisfy_all_comparators=0 fi - elif nvm_string_contains_regexp "$comparator" '^<[0-9]+\.[0-9]+\.[0-9]+$'; then - if nvm_version_greater "$stripped_version_from_comparator" "$current_remote_node_version"; then - node_version_compatible=$TRUE + elif nvm_string_contains_regexp "$current_comparator" '^<[0-9]+\.[0-9]+\.[0-9]+$'; then + if nvm_version_greater "$stripped_version_from_comparator" "$current_version"; then + is_current_version_compatible=0 else - node_version_compatible=$FALSE + is_current_version_compatible=1 fi - elif nvm_string_contains_regexp "$comparator" '^>[0-9]+\.[0-9]+\.[0-9]+$'; then - if nvm_version_greater "$current_remote_node_version" "$stripped_version_from_comparator"; then - node_version_compatible=$TRUE + elif nvm_string_contains_regexp "$current_comparator" '^>[0-9]+\.[0-9]+\.[0-9]+$'; then + if nvm_version_greater "$current_version" "$stripped_version_from_comparator"; then + is_current_version_compatible=0 else - node_version_compatible=$FALSE - no_remote_version_will_satisfy_all_comparators=$TRUE + is_current_version_compatible=1 + no_remote_version_will_satisfy_all_comparators=0 fi else - node_version_compatible=$FALSE + is_current_version_compatible=1 + fi + # If current_version is not compatible with current_comparator, stop iterating through current_comparator_set_copy. + if [ $is_current_version_compatible -eq 1 ]; then + current_comparator_set_copy='' + fi + # If determined that continuing iterating through version_list_copy will not find a version compatible with all comparators, stop iterating through version_list_copy. + if [ ${no_remote_version_will_satisfy_all_comparators-1} -eq 0 ]; then + version_list_copy='' fi - # stop checking if all comparators are compatible with current_remote_node_version upon finding one that is incompatible - [ $node_version_compatible -eq $FALSE ] && comparator_set_copy='' - done # while [ -n "$comparator_set_copy" ]; do - # stop iterating through remote_node_versions_copy for this comparator_set upon finding the first remote node version that is compatible with all comparators. - [ $node_version_compatible -eq $TRUE ] && highest_compatible_node_versions="$highest_compatible_node_versions $current_remote_node_version" - done # while [ -n "$remote_node_versions_copy" ] && [ $node_version_compatible -eq $FALSE ] && [ $no_remote_version_will_satisfy_all_comparators -eq $FALSE ]; do + done # while [ -n "$current_comparator_set_copy" ] && [ $no_remote_version_will_satisfy_all_comparators -eq 1 ]; do + # If the current_version is compatible with all comparators in current_comparator_set, add it to the list of highest_compatible_versions. + if [ $is_current_version_compatible -eq 0 ]; then + highest_compatible_versions="$highest_compatible_versions $current_version" + fi + done # while [ -n "$version_list_copy" ] && [ $is_current_version_compatible -eq 1 ]; do + done # while [ -n "$semver" ]; do + + # Iterate through each of the versions in highest_compatible_versions, which are the highest versions that satisfy each of the comparator sets. + # Since comparator sets are separated by '||', choosing any of the highest versions compatible with any of the comparator_sets would be compatible with the whole semver. + # Therefore, we should resolve to the highest version in highest_compatible_versions. + highest_compatible_version='0.0.0' + highest_compatible_versions=$(command printf "%s" "$highest_compatible_versions" | command tr ' ' '\n') + while [ -n "$highest_compatible_versions" ]; do + compatible_node_version=$(command printf "%s" "$highest_compatible_versions" | command head -n1 | command sed -E 's/^ +//;s/ +$//') + highest_compatible_versions=$(command printf "%s" "$highest_compatible_versions" | command tail -n +2) + [ -n "$compatible_node_version" ] || continue + if nvm_version_greater "$compatible_node_version" "$highest_compatible_version"; then + highest_compatible_version="$compatible_node_version" + fi done + if [ "$highest_compatible_version" != '0.0.0' ]; then + command printf "%s" "$highest_compatible_version" + fi +) } - highest_compatible_node_version='0.0.0' +# Given a semver and version list, optimize discovery of highest compatible version with this function which quickly interprets some common semvers. +# NOTE: Surrounding the function body in parens limits the scope of variables in this function to only this function. +nvm_interpret_simple_semver() { ( + semver="${1-}" + version_list="${2-}" # expected to be sorted from oldest to newest + if [ -z "$semver" ] || [ -z "$version_list" ]; then + nvm_err "Error interpretting simple semver: Missing required parameter(s)" + return 1 + fi + if ! nvm_string_contains_regexp "$semver" '^[<>=]*[0-9]+\.[0-9]+\.[0-9]+$'; then + # semver is not a semver with only a single comparator + return 1 + fi + stripped_version_from_semver="$(command printf "%s" "$semver" | nvm_grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+$')" + newest_version_from_list=$(command printf "%s" "$version_list" | tail -n 1 | nvm_grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+$') + if [ -z "$stripped_version_from_semver" ]; then + nvm_err "Error interpretting simple semver: error retrieving stripped version from semver" + return 1 + fi + # if the semver is looking for an exact match, and it exists in the provided list of versions, resolve to that version + if nvm_string_contains_regexp "$semver" '^[0-9]+\.[0-9]+\.[0-9]+$'; then + if nvm_string_contains_regexp "$version_list" "^v$stripped_version_from_semver$"; then + command printf "%s" "$stripped_version_from_semver" + return 0 + else + return 1 + fi - highest_compatible_node_versions=$(command printf "%s" "$highest_compatible_node_versions" | command tr ' ' '\n') - while [ -n "$highest_compatible_node_versions" ]; do - compatible_node_version=$(command printf "%s" "$highest_compatible_node_versions" | head -n1 | command sed -E 's/^ +//;s/ +$//') - highest_compatible_node_versions=$(command printf "%s" "$highest_compatible_node_versions" | tail -n +2) - [ -n "$compatible_node_version" ] || continue + # Semver is looking for the newest version that is <= to a sepcific version, and the version exists in the provided list of versions, resolve to that version + elif nvm_string_contains_regexp "$semver" '^<=[0-9]+\.[0-9]+\.[0-9]+$'; then + if nvm_string_contains_regexp "$version_list" "^v$stripped_version_from_semver$"; then + command printf "%s" "$stripped_version_from_semver" + return 0 + else + return 1 + fi - if nvm_version_greater "$compatible_node_version" "$highest_compatible_node_version"; then - highest_compatible_node_version="$compatible_node_version" + # Semver is looking for the newest version >= a specific version, and the newest version in the provided list of versions is >= the specified version, resolve to that version. + elif nvm_string_contains_regexp "$semver" '^>=[0-9]+\.[0-9]+\.[0-9]+$'; then + if nvm_version_greater_than_or_equal_to "$newest_version_from_list" "$stripped_version_from_semver"; then + command printf "%s" "$newest_version_from_list" + return 0 + else + return 1 fi - done - [ "$highest_compatible_node_version" != '0.0.0' ] && nvm_echo "$highest_compatible_node_version" + + elif nvm_string_contains_regexp "$semver" '^>[0-9]+\.[0-9]+\.[0-9]+$'; then + if nvm_version_greater "$newest_version_from_list" "$stripped_version_from_semver"; then + command printf "%s" "$newest_version_from_list" + return 0 + else + return 1 + fi + + else + return 1 + fi ) } -nvm_find_package_json() { ( +# Given a semantic version, resolve it to the newest compatible remote node version. +# NOTE: Surrounding the function body in parens limits the scope of variables in this function to only this function. +nvm_interpret_semver() { ( + semver="${1-}" + if [ -z "$semver" ]; then + nvm_err "Error interpretting semver: Missing semver parameter" + return 1 + fi + + # Validate incoming semver and transform it into the grammar that is expected by the following logic + valid_transformed_semver=$(nvm_validate_semver "$semver") + if [ -z "$valid_transformed_semver" ]; then + nvm_err "Error interpretting semver: invalid semver: '$semver'" + return 1 + fi + + # TODO add tests verifying this logic correctly gets list of node versions and that the order is from oldest to newest + # list of node versions is sorted from oldest to newest + remote_node_versions=$(nvm_ls_remote | nvm_grep -o 'v[0-9]\+\.[0-9]\+\.[0-9]\+') + if [ -z "$remote_node_versions" ]; then + nvm_err "Error interpretting semver: failure retrieving remote node versions" + return 1 + fi + + # If semver is a single comparator, use quick algorithm to determine newest compatible version + resolved_version=$(nvm_interpret_simple_semver "$valid_transformed_semver" "$remote_node_versions") + if [ -n "$resolved_version" ]; then + command printf "%s" "$resolved_version" + return 0 + fi + + # If semver is a semver with > 1 comparator, iterate through each remote node version from newest to oldest until finding the newest version compatible with all comparators. + resolved_version=$(nvm_interpret_complex_semver "$valid_transformed_semver" "$remote_node_versions") + if [ -n "$resolved_version" ]; then + command printf "%s" "$resolved_version" + return 0 + fi + + return 1 +) } + +nvm_find_package_json() { dir="$(nvm_find_up 'package.json')" if [ -e "${dir}/package.json" ]; then - nvm_echo "${dir}/package.json" + command printf "%s" "${dir}/package.json" fi -) } +} # extracts engines.node value from package.json exactly except: # - removes all line breaks and carriage returns # - normalizes all consecutive whitespace to 1 occurrence -# - must match regexp: "[|<> [:alnum:].^=~*-]\+" +# - semantic expression must match regexp: "[|<> [:alnum:].^=~*-]\+" +# NOTE: Surrounding the function body in parens limits the scope of variables in this function to only this function. nvm_get_node_from_pkg_json() { ( package_json_contents=${1-} engines_node_value='' open_brackets=0 closed_brackets=0 - in_quotes='false' + in_quotes=1 command printf "%s" "$package_json_contents" \ | command tr -d '\n\r' \ @@ -601,15 +731,15 @@ nvm_get_node_from_pkg_json() { ( | while read -r i; do engines_node_value="$engines_node_value$i" if [ "$i" = '"' ]; then - if [ "$in_quotes" = 'false' ]; then - in_quotes='true' + if [ "$in_quotes" = 1 ]; then + in_quotes=0 else - in_quotes='false' + in_quotes=1 fi # spaces are interpretted as '' here but they need to be retained elif [ "$i" = '' ]; then engines_node_value="$engines_node_value " - elif [ "$in_quotes" = 'false' ]; then + elif [ "$in_quotes" = 1 ]; then if [ "$i" = '{' ]; then open_brackets=$((open_brackets+1)) elif [ "$i" = '}' ]; then @@ -629,23 +759,24 @@ nvm_get_node_from_pkg_json() { ( ) } nvm_package_json_version() { - export PKG_JSON_VERSION='' + export RESOLVED_PKG_JSON_VERSION='' local pkg_json_path pkg_json_path="$(nvm_find_package_json)" if [ ! -e "${pkg_json_path}" ]; then nvm_err "No package.json file found" return 1 fi - PKG_JSON_VERSION=$(nvm_get_node_from_pkg_json "$(command cat "$pkg_json_path")" || command printf '') - if [ ! -n "${PKG_JSON_VERSION}" ]; then - nvm_echo "Warning: could not retrieve engines.node semver expression in package.json file found at \"${pkg_json_path}\"" + local pkg_json_semver + pkg_json_semver=$(nvm_get_node_from_pkg_json "$(command cat "$pkg_json_path")" || command printf '') + if [ ! -n "${pkg_json_semver}" ]; then + nvm_err "Warning: could not retrieve engines.node semver expression in package.json file found at \"${pkg_json_path}\"" return 2 else - nvm_echo "Found '${pkg_json_path}' with semver expression <${PKG_JSON_VERSION}>" + nvm_echo "Found '${pkg_json_path}' with semver expression <${pkg_json_semver}>" # attempt complex semver range evaluation - PKG_JSON_VERSION=$(nvm_interpret_complex_semver "$PKG_JSON_VERSION") - if [ ! -n "${PKG_JSON_VERSION}" ]; then - nvm_echo "Warning: could not interpret engines.node semver expression obtained from package.json file." + RESOLVED_PKG_JSON_VERSION=$(nvm_interpret_semver "$pkg_json_semver") + if [ ! -n "${RESOLVED_PKG_JSON_VERSION}" ]; then + nvm_err "Warning: could not interpret engines.node semver expression obtained from package.json file." return 2 fi fi @@ -2951,16 +3082,16 @@ nvm() { nvm_rc_version || nvm_package_json_version if [ -n "${NVM_RC_VERSION-}" ]; then provided_version="$NVM_RC_VERSION" - elif [ -n "${PKG_JSON_VERSION-}" ]; then - provided_version="$PKG_JSON_VERSION" + elif [ -n "${RESOLVED_PKG_JSON_VERSION-}" ]; then + provided_version="$RESOLVED_PKG_JSON_VERSION" elif [ $version_not_provided -eq 1 ]; then unset NVM_RC_VERSION - unset PKG_JSON_VERSION + unset RESOLVED_PKG_JSON_VERSION >&2 nvm --help return 127 fi unset NVM_RC_VERSION - unset PKG_JSON_VERSION + unset RESOLVED_PKG_JSON_VERSION fi elif [ $# -gt 0 ]; then shift @@ -3307,17 +3438,17 @@ nvm() { nvm_rc_version || nvm_package_json_version if [ -n "${NVM_RC_VERSION-}" ]; then PROVIDED_VERSION="$NVM_RC_VERSION" - elif [ -n "${PKG_JSON_VERSION-}" ]; then - PROVIDED_VERSION="$PKG_JSON_VERSION" + elif [ -n "${RESOLVED_PKG_JSON_VERSION-}" ]; then + PROVIDED_VERSION="$RESOLVED_PKG_JSON_VERSION" else unset NVM_RC_VERSION - unset PKG_JSON_VERSION + unset RESOLVED_PKG_JSON_VERSION >&2 nvm --help return 127 fi VERSION="$(nvm_version "$PROVIDED_VERSION")" unset NVM_RC_VERSION - unset PKG_JSON_VERSION + unset RESOLVED_PKG_JSON_VERSION else VERSION="$(nvm_match_version "$PROVIDED_VERSION")" fi @@ -3909,8 +4040,8 @@ nvm() { node_version_has_solaris_binary iojs_version_has_solaris_binary \ nvm_curl_libz_support nvm_command_info \ nvm_get_node_from_pkg_json nvm_find_package_json nvm_package_json_version \ - nvm_interpret_complex_semver nvm_validate_semver \ - nvm_string_contains_regexp \ + nvm_interpret_semver nvm_interpret_simple_semver nvm_interpret_complex_semver nvm_validate_semver \ + nvm_is_valid_semver nvm_string_contains_regexp \ > /dev/null 2>&1 unset NVM_RC_VERSION NVM_NODEJS_ORG_MIRROR NVM_IOJS_ORG_MIRROR NVM_DIR \ NVM_CD_FLAGS NVM_BIN NVM_MAKE_JOBS \ diff --git a/test/fast/Unit tests/nvm_interpret_complex_semver b/test/fast/Unit tests/nvm_interpret_semver similarity index 77% rename from test/fast/Unit tests/nvm_interpret_complex_semver rename to test/fast/Unit tests/nvm_interpret_semver index ec06286e7e..e6eab3f984 100755 --- a/test/fast/Unit tests/nvm_interpret_complex_semver +++ b/test/fast/Unit tests/nvm_interpret_semver @@ -1,5 +1,7 @@ #!/bin/sh +# TODO this test currently takes about 1 minute and will take even longer with the rest of the needed test cases so maybe it shouldn't live in the "fast" tests directory. + die () { printf "$@" ; exit 1; } \. ../../../nvm.sh @@ -27,7 +29,7 @@ while [ -n "$test_cases" ]; do LINE=$(echo "$test_cases" | head -n1) INPUT=$(echo "$LINE" | awk -F: '{ print $1 }') EXPECTED_OUTPUT=$(echo "$LINE" | awk -F: '{ print $2 }') - ACTUAL_OUTPUT=$(nvm_interpret_complex_semver "$INPUT") + ACTUAL_OUTPUT=$(nvm_interpret_semver "$INPUT") [ "$ACTUAL_OUTPUT" = "$EXPECTED_OUTPUT" ] \ && [ -n "$INPUT" ] \ || die "Expected output: '$EXPECTED_OUTPUT'. Actual output: '$ACTUAL_OUTPUT'. Input: '$INPUT'.\n" From 10e010eb8030c8ac8b6c489724a8f480031f0287 Mon Sep 17 00:00:00 2001 From: edwmurph Date: Mon, 28 May 2018 08:40:04 -0400 Subject: [PATCH 03/12] fix lint errors; updated semver interpretation documentation --- .editorconfig | 7 ++ nvm.sh | 8 +- semver.md | 90 ++++++++++--------- .../Unit tests/nvm_get_node_from_pkg_json | 14 +-- 4 files changed, 64 insertions(+), 55 deletions(-) diff --git a/.editorconfig b/.editorconfig index 32de1923c1..b368348c0a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -14,3 +14,10 @@ indent_size = false [Makefile] indent_style = tab + +[test/fast/Unit tests/package_json_templates/*] +indent_style = unset + +[test/fast/Unit tests/nvm_get_node_from_pkg_json] +indent_style = unset +indent_size = unset diff --git a/nvm.sh b/nvm.sh index 8d032c1223..31faaee3f3 100644 --- a/nvm.sh +++ b/nvm.sh @@ -328,7 +328,7 @@ nvm_is_valid_semver() { # semver ::= comparator_set ( ' || ' comparator_set )* # comparator_set ::= comparator ( ' ' comparator )* # comparator ::= ( '<' | '<=' | '>' | '>=' | '' ) [0-9]+ '.' [0-9]+ '.' [0-9]+ -# +# # NOTE: Surrounding the function body in parens limits the scope of variables in this function to only this function. nvm_validate_semver() { ( # split the semantic version into comparator_set's @@ -512,7 +512,7 @@ nvm_interpret_complex_semver() { ( is_current_version_compatible=1 # initialized to false # For each comparator in the current_comparator_set_copy: - # - If current_version is compatible with all comparators, we know current_version is the newest compatible version + # - If current_version is compatible with all comparators, we know current_version is the newest compatible version current_comparator_set_copy=$(command printf "%s" "$current_comparator_set" | command tr ' ' '\n') while [ -n "$current_comparator_set_copy" ]; do current_comparator=$(command printf "%s" "$current_comparator_set_copy" | command head -n1 | command sed -E 's/^ +//;s/ +$//') @@ -733,9 +733,9 @@ nvm_get_node_from_pkg_json() { ( if [ "$i" = '"' ]; then if [ "$in_quotes" = 1 ]; then in_quotes=0 - else + else in_quotes=1 - fi + fi # spaces are interpretted as '' here but they need to be retained elif [ "$i" = '' ]; then engines_node_value="$engines_node_value " diff --git a/semver.md b/semver.md index 709d4a1da7..bb4d68ce4e 100644 --- a/semver.md +++ b/semver.md @@ -1,50 +1,52 @@ -# Node semantic version interpretation documentation - -Node versions are dynamically interpretted from the semver expression that is extracted from the local package.json file. The algorithm for doing this is expressed below. Each step is isolated into its own function to make testing and debugging easier. - -## 1. Extract the semver expression located in the engines.node value of the local package.json file. - -#### Required input grammar for semver expression extracted from package.json: - -> Grammar is copied from https://docs.npmjs.com/misc/semver -> ``` -> range-set ::= range ( logical-or range ) * -> logical-or ::= ( ' ' ) * '||' ( ' ' ) * -> range ::= hyphen | simple ( ' ' simple ) * | '' -> hyphen ::= partial ' - ' partial -> simple ::= primitive | partial | tilde | caret -> primitive ::= ( '<' | '>' | '>=' | '<=' | '=' | ) partial -> partial ::= xr ( '.' xr ( '.' xr qualifier ? )? )? -> xr ::= 'x' | 'X' | '*' | nr -> nr ::= '0' | ['1'-'9'] ( ['0'-'9'] ) * -> tilde ::= '~' partial -> caret ::= '^' partial -> qualifier ::= ( '-' pre )? ( '+' build )? -> pre ::= parts -> build ::= parts -> parts ::= part ( '.' part ) * -> part ::= nr | [-0-9A-Za-z]+ -> ``` -> Lazy grammar validation is used at this point. Basically any string in the engines.node value will be accepted at this point that matches the following regexp: -> "[|<> [:alnum:].^=~*-]\+" -> -> NOTE: all whitespace inside the engines.node value is normalized to be a single space in this step. - -## 2. Check that the extracted semver expression from the previous step matches the above grammar. If so, normalize it into the following grammar that is expected by the interpretation logic. - -#### Required input grammar for internal interpretation logic: +# Semantic version interpretation documentation + +Node versions are interpretted from the semver expression located in the engines.node value of the local package.json file. The algorithm for interpretting the semver is expressed at a high level below and is intended to work with the [semantic versioner for npm](https://docs.npmjs.com/misc/semver). + +## 1. Convert the semver into the following grammar: > ``` > semver ::= comparator_set ( ' || ' comparator_set )* > comparator_set ::= comparator ( ' ' comparator )* > comparator ::= ( '<' | '<=' | '>' | '>=' | '' ) [0-9]+ '.' [0-9]+ '.' [0-9]+ > ``` - -## 3. Interpret the normalized semver expression. - -> 1. Resolve each comparator set to the newest compatible node version -> - iterate through node versions from newest to oldest -> - find the first node version that satisfies all comparators -> - if reached a point where no older node versions will satisfy comparator, stop iterating through node versions. -> 2. Choose the newest node version among the resolved node versions from the previous step. - + +## 2. Resolve each comparator set to its newest compatible node version + +**First, if semver only contains a single comparator_set, we may be able to quickly find the newest compatible version.** + +```pseudocode +if semver is looking for an exact match of some specified version and the specified version is a valid node version + resolve to the specified version +else if semver is looking for a version less than or equal to some specified version and the specified version is a valid node version + resolve to the specified version +else if semver is looking for a version greater than or equal to some specified version and the current newest node version is greater than or equal to the specified version + resolve to the current newest node version +else if semver is looking for a version strictly greater than to some specified version and the current newest node version is greater than the specified version + resolve to the current newest node version +else + quick resolution of semver interpretation not possible +``` + +**If quick resolution of semver interpretation does not work, try more complex semver interpretation algorithm.** + +```pseudocode +initialize highest_compatible_versions to an empty list +initialize node_version_list to the list of current remote node versions +for each current_comparator_set in the semver { + for each current_node_version in node_version_list { + for each current_comparator in current_comparator_set { + if current_node_version is compatible with current_comparator + continue seeing if current_node_version might be compatible with all comparators in current_comparator_set + else if current_node_version is not compatible with current_comparator + if it can be determined that no older version will satisfy this comparator, we can move on to the next comparator_set in the semver + else + stop seeing if current_node_version is compatible with all comparators in current_comparator_set and move on to the next version + } + if current_node_version was found to be compatible with all comparators in current_comparator_set + add current_node_version to the highest_compatible_versions list + } +} + +resolve to the highest version among all the versions collected in highest_compatible_versions +``` + diff --git a/test/fast/Unit tests/nvm_get_node_from_pkg_json b/test/fast/Unit tests/nvm_get_node_from_pkg_json index 25526cbcad..d5cc6ae59e 100755 --- a/test/fast/Unit tests/nvm_get_node_from_pkg_json +++ b/test/fast/Unit tests/nvm_get_node_from_pkg_json @@ -128,9 +128,9 @@ TEST_SEMVERS_COPY=$(echo "$TEST_SEMVERS") PREV_TEST_SEMVER='' for TEMPLATE_NAME in package_json_templates/_valid_*; do while [ -n "$TEST_SEMVERS_COPY" ]; do - LINE=$(echo "$TEST_SEMVERS_COPY" | head -n1) + LINE=$(echo "$TEST_SEMVERS_COPY" | head -n1) TEST_SEMVER_INPUT=$(echo "$LINE" | awk -F: '{ print $1 }') - EXPECTED_OUTPUT=$(echo "$LINE" | awk -F: '{ print $2 }') + EXPECTED_OUTPUT=$(echo "$LINE" | awk -F: '{ print $2 }') TEST_SEMVERS_COPY=$(echo "$TEST_SEMVERS_COPY" | tail -n +2) [ "$PREV_TEST_SEMVER" = "$TEST_SEMVER_INPUT" ] \ @@ -143,7 +143,7 @@ for TEMPLATE_NAME in package_json_templates/_valid_*; do [ "$ACTUAL_OUTPUT" = "$EXPECTED_OUTPUT" ] \ && [ -n "$ACTUAL_OUTPUT" ] \ && [ -n "$PKG_JSON_CONTENTS" ] \ - || die "'nvm_get_node_from_pkg_json' POSITIVE test case failed (TEST SET #1). + || die "'nvm_get_node_from_pkg_json' POSITIVE test case failed (TEST SET #1). Expected '$EXPECTED_OUTPUT' but got '$ACTUAL_OUTPUT' when given input template '$TEMPLATE_NAME':\n$PKG_JSON_CONTENTS" done done @@ -157,7 +157,7 @@ TEST_SEMVERS_COPY=$(echo "$TEST_SEMVERS") PREV_TEST_SEMVER='' for TEMPLATE_NAME in package_json_templates/_invalid_*; do while [ -n "$TEST_SEMVERS_COPY" ]; do - LINE=$(echo "$TEST_SEMVERS_COPY" | head -n1) + LINE=$(echo "$TEST_SEMVERS_COPY" | head -n1) TEST_SEMVER_INPUT=$(echo "$LINE" | awk -F: '{ print $1 }') TEST_SEMVERS_COPY=$(echo "$TEST_SEMVERS_COPY" | tail -n +2) @@ -171,7 +171,7 @@ for TEMPLATE_NAME in package_json_templates/_invalid_*; do [ "$ACTUAL_OUTPUT" = "" ] \ && [ -n "$TEST_SEMVER_INPUT" ] \ && [ -n "$PKG_JSON_CONTENTS" ] \ - || die "'nvm_get_node_from_pkg_json' NEGATIVE test case failed (TEST SET #2). + || die "'nvm_get_node_from_pkg_json' NEGATIVE test case failed (TEST SET #2). Expected to get empty string but got '$ACTUAL_OUTPUT' when given input template '$TEMPLATE_NAME':\n$PKG_JSON_CONTENTS" done done @@ -213,7 +213,7 @@ for TEMPLATE_NAME in package_json_templates/_valid_*; do [ "$ACTUAL_OUTPUT" = "" ] \ && [ -n "$TEST_SEMVER_INPUT" ] \ && [ -n "$PKG_JSON_CONTENTS" ] \ - || die "'nvm_get_node_from_pkg_json' NEGATIVE test case failed (TEST SET #3). + || die "'nvm_get_node_from_pkg_json' NEGATIVE test case failed (TEST SET #3). Expected to get empty string but got '$ACTUAL_OUTPUT' when given input template '$TEMPLATE_NAME':\n$PKG_JSON_CONTENTS" done done @@ -236,7 +236,7 @@ for TEMPLATE_NAME in package_json_templates/_invalid_*; do [ "$ACTUAL_OUTPUT" = "" ] \ && [ -n "$TEST_SEMVER_INPUT" ] \ && [ -n "$PKG_JSON_CONTENTS" ] \ - || die "'nvm_get_node_from_pkg_json' NEGATIVE test case failed (TEST SET #4). + || die "'nvm_get_node_from_pkg_json' NEGATIVE test case failed (TEST SET #4). Expected to get empty string but got '$ACTUAL_OUTPUT' when given input template '$TEMPLATE_NAME':\n$PKG_JSON_CONTENTS" done done From c996ac7c5ca17d8f602dfdb32df7682aa5c8e771 Mon Sep 17 00:00:00 2001 From: edwmurph Date: Mon, 28 May 2018 12:19:39 -0400 Subject: [PATCH 04/12] refactored tests --- .../Unit tests/nvm_get_node_from_pkg_json | 65 +++++++++---------- test/fast/Unit tests/nvm_interpret_semver | 6 +- test/fast/Unit tests/nvm_string_equals_regexp | 13 ++-- test/fast/Unit tests/nvm_validate_semver | 7 +- 4 files changed, 43 insertions(+), 48 deletions(-) diff --git a/test/fast/Unit tests/nvm_get_node_from_pkg_json b/test/fast/Unit tests/nvm_get_node_from_pkg_json index d5cc6ae59e..c9e2f01832 100755 --- a/test/fast/Unit tests/nvm_get_node_from_pkg_json +++ b/test/fast/Unit tests/nvm_get_node_from_pkg_json @@ -114,10 +114,7 @@ xx:xx # - validate that leading trailing spaces and tabs are removed TEST_SEMVERS="$TEST_SEMVERS 1 1 :1 1 - 1 1 :1 1" - -# remove first line which is just an empty line -TEST_SEMVERS=$(echo "$TEST_SEMVERS" | tail -n +2) + 2 2 :2 2" # POSITIVE TEST CASES @@ -129,22 +126,22 @@ PREV_TEST_SEMVER='' for TEMPLATE_NAME in package_json_templates/_valid_*; do while [ -n "$TEST_SEMVERS_COPY" ]; do LINE=$(echo "$TEST_SEMVERS_COPY" | head -n1) + TEST_SEMVERS_COPY=$(echo "$TEST_SEMVERS_COPY" | tail -n +2) + [ -n "$LINE" ] || continue TEST_SEMVER_INPUT=$(echo "$LINE" | awk -F: '{ print $1 }') EXPECTED_OUTPUT=$(echo "$LINE" | awk -F: '{ print $2 }') - TEST_SEMVERS_COPY=$(echo "$TEST_SEMVERS_COPY" | tail -n +2) - [ "$PREV_TEST_SEMVER" = "$TEST_SEMVER_INPUT" ] \ - && die "Problem iterating through TEST_SEMVERS_COPY (TEST SET #1). Encountered the same value twice in a row. PREV_TEST_SEMVER='$PREV_TEST_SEMVER' TEST_SEMVER_INPUT='$TEST_SEMVER_INPUT'.\n" + if [ "$PREV_TEST_SEMVER" == "$TEST_SEMVER_INPUT" ]; then + die "Problem iterating through TEST_SEMVERS_COPY (TEST SET #1). Encountered the same value twice in a row. PREV_TEST_SEMVER='$PREV_TEST_SEMVER' TEST_SEMVER_INPUT='$TEST_SEMVER_INPUT'.\n" + fi PREV_TEST_SEMVER=$(printf "%s" "$TEST_SEMVER_INPUT") PKG_JSON_CONTENTS=$(sed 's/NODE_SEMVER/'"$TEST_SEMVER_INPUT"'/g' "$TEMPLATE_NAME" | tail -n +3) ACTUAL_OUTPUT=$(nvm_get_node_from_pkg_json "$PKG_JSON_CONTENTS") - [ "$ACTUAL_OUTPUT" = "$EXPECTED_OUTPUT" ] \ - && [ -n "$ACTUAL_OUTPUT" ] \ - && [ -n "$PKG_JSON_CONTENTS" ] \ - || die "'nvm_get_node_from_pkg_json' POSITIVE test case failed (TEST SET #1). - Expected '$EXPECTED_OUTPUT' but got '$ACTUAL_OUTPUT' when given input template '$TEMPLATE_NAME':\n$PKG_JSON_CONTENTS" + if [ "$ACTUAL_OUTPUT" != "$EXPECTED_OUTPUT" ] || [ -z "$ACTUAL_OUTPUT" ] || [ -z "$PKG_JSON_CONTENTS" ]; then + die "'nvm_get_node_from_pkg_json' POSITIVE test case failed (TEST SET #1). Expected '$EXPECTED_OUTPUT' but got '$ACTUAL_OUTPUT' when given input template '$TEMPLATE_NAME':\n$PKG_JSON_CONTENTS" + fi done done @@ -158,21 +155,21 @@ PREV_TEST_SEMVER='' for TEMPLATE_NAME in package_json_templates/_invalid_*; do while [ -n "$TEST_SEMVERS_COPY" ]; do LINE=$(echo "$TEST_SEMVERS_COPY" | head -n1) - TEST_SEMVER_INPUT=$(echo "$LINE" | awk -F: '{ print $1 }') TEST_SEMVERS_COPY=$(echo "$TEST_SEMVERS_COPY" | tail -n +2) + [ -n "$LINE" ] || continue + TEST_SEMVER_INPUT=$(echo "$LINE" | awk -F: '{ print $1 }') - [ "$PREV_TEST_SEMVER" = "$TEST_SEMVER_INPUT" ] \ - && die "Problem iterating through TEST_SEMVERS_COPY (TEST SET #2). Encountered the same value twice in a row. PREV_TEST_SEMVER='$PREV_TEST_SEMVER' TEST_SEMVER_INPUT='$TEST_SEMVER_INPUT'.\n" + if [ "$PREV_TEST_SEMVER" == "$TEST_SEMVER_INPUT" ]; then + die "Problem iterating through TEST_SEMVERS_COPY (TEST SET #2). Encountered the same value twice in a row. PREV_TEST_SEMVER='$PREV_TEST_SEMVER' TEST_SEMVER_INPUT='$TEST_SEMVER_INPUT'.\n" + fi PREV_TEST_SEMVER=$(printf "%s" "$TEST_SEMVER_INPUT") PKG_JSON_CONTENTS=$(sed 's/NODE_SEMVER/'"$TEST_SEMVER_INPUT"'/g' "$TEMPLATE_NAME" | tail -n +3) ACTUAL_OUTPUT=$(nvm_get_node_from_pkg_json "$PKG_JSON_CONTENTS") - [ "$ACTUAL_OUTPUT" = "" ] \ - && [ -n "$TEST_SEMVER_INPUT" ] \ - && [ -n "$PKG_JSON_CONTENTS" ] \ - || die "'nvm_get_node_from_pkg_json' NEGATIVE test case failed (TEST SET #2). - Expected to get empty string but got '$ACTUAL_OUTPUT' when given input template '$TEMPLATE_NAME':\n$PKG_JSON_CONTENTS" + if [ "$ACTUAL_OUTPUT" != "" ] || [ -z "$TEST_SEMVER_INPUT" ] || [ -z "$PKG_JSON_CONTENTS" ]; then + die "'nvm_get_node_from_pkg_json' NEGATIVE test case failed (TEST SET #2). Expected to get empty string but got '$ACTUAL_OUTPUT' when given input template '$TEMPLATE_NAME':\n$PKG_JSON_CONTENTS" + fi done done @@ -201,20 +198,20 @@ PREV_TEST_SEMVER='' for TEMPLATE_NAME in package_json_templates/_valid_*; do while [ -n "$TEST_SEMVERS_COPY" ]; do TEST_SEMVER_INPUT=$(echo "$TEST_SEMVERS_COPY" | head -n1) + [ -n "$TEST_SEMVER_INPUT" ] || continue TEST_SEMVERS_COPY=$(echo "$TEST_SEMVERS_COPY" | tail -n +2) - [ "$PREV_TEST_SEMVER" = "$TEST_SEMVER_INPUT" ] \ - && die "Problem iterating through TEST_SEMVERS_COPY (TEST SET #3). Encountered the same value twice in a row. PREV_TEST_SEMVER='$PREV_TEST_SEMVER' TEST_SEMVER_INPUT='$TEST_SEMVER_INPUT'.\n" + if [ "$PREV_TEST_SEMVER" == "$TEST_SEMVER_INPUT" ]; then + die "Problem iterating through TEST_SEMVERS_COPY (TEST SET #3). Encountered the same value twice in a row. PREV_TEST_SEMVER='$PREV_TEST_SEMVER' TEST_SEMVER_INPUT='$TEST_SEMVER_INPUT'.\n" + fi PREV_TEST_SEMVER=$(printf "%s" "$TEST_SEMVER_INPUT") PKG_JSON_CONTENTS=$(sed 's/NODE_SEMVER/'"$TEST_SEMVER_INPUT"'/g' "$TEMPLATE_NAME" | tail -n +3) ACTUAL_OUTPUT=$(nvm_get_node_from_pkg_json "$PKG_JSON_CONTENTS") - [ "$ACTUAL_OUTPUT" = "" ] \ - && [ -n "$TEST_SEMVER_INPUT" ] \ - && [ -n "$PKG_JSON_CONTENTS" ] \ - || die "'nvm_get_node_from_pkg_json' NEGATIVE test case failed (TEST SET #3). - Expected to get empty string but got '$ACTUAL_OUTPUT' when given input template '$TEMPLATE_NAME':\n$PKG_JSON_CONTENTS" + if [ "$ACTUAL_OUTPUT" != "" ] || [ -z "$TEST_SEMVER_INPUT" ] || [ -z "$PKG_JSON_CONTENTS" ]; then + die "'nvm_get_node_from_pkg_json' NEGATIVE test case failed (TEST SET #3). Expected to get empty string but got '$ACTUAL_OUTPUT' when given input template '$TEMPLATE_NAME':\n$PKG_JSON_CONTENTS" + fi done done @@ -224,19 +221,19 @@ PREV_TEST_SEMVER='' for TEMPLATE_NAME in package_json_templates/_invalid_*; do while [ -n "$TEST_SEMVERS_COPY" ]; do TEST_SEMVER_INPUT=$(echo "$TEST_SEMVERS_COPY" | head -n1) + [ -n "$TEST_SEMVER_INPUT" ] || continue TEST_SEMVERS_COPY=$(echo "$TEST_SEMVERS_COPY" | tail -n +2) - [ "$PREV_TEST_SEMVER" = "$TEST_SEMVER_INPUT" ] \ - && die "Problem iterating through TEST_SEMVERS_COPY (TEST SET #4). Encountered the same value twice in a row. PREV_TEST_SEMVER='$PREV_TEST_SEMVER' TEST_SEMVER_INPUT='$TEST_SEMVER_INPUT'.\n" + if [ "$PREV_TEST_SEMVER" == "$TEST_SEMVER_INPUT" ]; then + die "Problem iterating through TEST_SEMVERS_COPY (TEST SET #4). Encountered the same value twice in a row. PREV_TEST_SEMVER='$PREV_TEST_SEMVER' TEST_SEMVER_INPUT='$TEST_SEMVER_INPUT'.\n" + fi PREV_TEST_SEMVER=$(printf "%s" "$TEST_SEMVER_INPUT") PKG_JSON_CONTENTS=$(sed 's/NODE_SEMVER/'"$TEST_SEMVER_INPUT"'/g' "$TEMPLATE_NAME" | tail -n +3) ACTUAL_OUTPUT=$(nvm_get_node_from_pkg_json "$PKG_JSON_CONTENTS") - [ "$ACTUAL_OUTPUT" = "" ] \ - && [ -n "$TEST_SEMVER_INPUT" ] \ - && [ -n "$PKG_JSON_CONTENTS" ] \ - || die "'nvm_get_node_from_pkg_json' NEGATIVE test case failed (TEST SET #4). - Expected to get empty string but got '$ACTUAL_OUTPUT' when given input template '$TEMPLATE_NAME':\n$PKG_JSON_CONTENTS" + if [ "$ACTUAL_OUTPUT" != "" ] || [ -z "$TEST_SEMVER_INPUT" ] || [ -z "$PKG_JSON_CONTENTS" ]; then + die "'nvm_get_node_from_pkg_json' NEGATIVE test case failed (TEST SET #4). Expected to get empty string but got '$ACTUAL_OUTPUT' when given input template '$TEMPLATE_NAME':\n$PKG_JSON_CONTENTS" + fi done done diff --git a/test/fast/Unit tests/nvm_interpret_semver b/test/fast/Unit tests/nvm_interpret_semver index e6eab3f984..e7d5a6431b 100755 --- a/test/fast/Unit tests/nvm_interpret_semver +++ b/test/fast/Unit tests/nvm_interpret_semver @@ -30,9 +30,9 @@ while [ -n "$test_cases" ]; do INPUT=$(echo "$LINE" | awk -F: '{ print $1 }') EXPECTED_OUTPUT=$(echo "$LINE" | awk -F: '{ print $2 }') ACTUAL_OUTPUT=$(nvm_interpret_semver "$INPUT") - [ "$ACTUAL_OUTPUT" = "$EXPECTED_OUTPUT" ] \ - && [ -n "$INPUT" ] \ - || die "Expected output: '$EXPECTED_OUTPUT'. Actual output: '$ACTUAL_OUTPUT'. Input: '$INPUT'.\n" + if [ "$ACTUAL_OUTPUT" != "$EXPECTED_OUTPUT" ] || [ -z "$INPUT" ]; then + die "Expected output: '$EXPECTED_OUTPUT'. Actual output: '$ACTUAL_OUTPUT'. Input: '$INPUT'.\n" + fi test_cases=$(echo "$test_cases" | tail -n +2) done exit 0 diff --git a/test/fast/Unit tests/nvm_string_equals_regexp b/test/fast/Unit tests/nvm_string_equals_regexp index 3ca56378ae..8239251e4a 100755 --- a/test/fast/Unit tests/nvm_string_equals_regexp +++ b/test/fast/Unit tests/nvm_string_equals_regexp @@ -16,10 +16,9 @@ while [ -n "$test_cases" ]; do LINE=$(echo "$test_cases" | head -n1) STRING=$(echo "$LINE" | awk -F: '{ print $1 }') REGEXP=$(echo "$LINE" | awk -F: '{ print $2 }') - [ -n "$REGEXP" ] \ - && [ -n "$STRING" ] \ - && nvm_string_contains_regexp "$STRING" "$REGEXP" \ - || die "nvm_string_contains_regexp POSITIVE test case failed. REGEXP: '$REGEXP'. STRING: '$STRING'.\n" + if [ -z "$REGEXP" ] || [ -z "$STRING" ] || ! nvm_string_contains_regexp "$STRING" "$REGEXP"; then + die "nvm_string_contains_regexp POSITIVE test case failed. REGEXP: '$REGEXP'. STRING: '$STRING'.\n" + fi test_cases=$(echo "$test_cases" | tail -n +2) done @@ -34,9 +33,9 @@ while [ -n "$test_cases" ]; do LINE=$(echo "$test_cases" | head -n1) STRING=$(echo "$LINE" | awk -F: '{ print $1 }') REGEXP=$(echo "$LINE" | awk -F: '{ print $2 }') - [ -n "$REGEXP" ] \ - && ! nvm_string_contains_regexp "$STRING" "$REGEXP" \ - || die "nvm_string_contains_regexp NEGATIVE test case failed. REGEXP: '$REGEXP'. STRING: '$STRING'.\n" + if [ -z "$REGEXP" ] || nvm_string_contains_regexp "$STRING" "$REGEXP"; then + die "nvm_string_contains_regexp NEGATIVE test case failed. REGEXP: '$REGEXP'. STRING: '$STRING'.\n" + fi test_cases=$(echo "$test_cases" | tail -n +2) done exit 0 diff --git a/test/fast/Unit tests/nvm_validate_semver b/test/fast/Unit tests/nvm_validate_semver index ec00041b97..a5cc64b4eb 100755 --- a/test/fast/Unit tests/nvm_validate_semver +++ b/test/fast/Unit tests/nvm_validate_semver @@ -5,7 +5,6 @@ die () { printf "$@" ; exit 1; } \. ../../../nvm.sh # INPUT:EXPECTED_OUTPUT -# TODO refactor test cases into clearer structure test_cases="1.2.3:1.2.3 1.2.3 - 1.2.4:>=1.2.3 <=1.2.4 1.2.3-1.2.4:>=1.2.3 <=1.2.4 @@ -46,9 +45,9 @@ while [ -n "$test_cases" ]; do INPUT=$(echo "$LINE" | awk -F: '{ print $1 }') EXPECTED_OUTPUT=$(echo "$LINE" | awk -F: '{ print $2 }') ACTUAL_OUTPUT=$(nvm_validate_semver "$INPUT") - [ "$ACTUAL_OUTPUT" = "$EXPECTED_OUTPUT" ] \ - && [ -n "$INPUT" ] \ - || die "Expected output: '$EXPECTED_OUTPUT'. Actual output: '$ACTUAL_OUTPUT'. Input: '$INPUT'.\n" + if [ -z "$INPUT" ] || [ "$ACTUAL_OUTPUT" != "$EXPECTED_OUTPUT" ]; then + die "nvm_validate_semver test case failed. Expected output: '$EXPECTED_OUTPUT'. Actual output: '$ACTUAL_OUTPUT'. Input: '$INPUT'.\n" + fi test_cases=$(echo "$test_cases" | tail -n +2) done exit 0 From 79319ea7a30d7933a45d00cfaccffa7be47bbc1f Mon Sep 17 00:00:00 2001 From: edwmurph Date: Mon, 28 May 2018 21:46:18 -0400 Subject: [PATCH 05/12] refactored nvm_interpret_complex_semver to clearer structure; removed semver.md --- nvm.sh | 127 +++++++++--------- semver.md | 52 ------- .../Unit tests/nvm_get_node_from_pkg_json | 8 +- test/fast/Unit tests/nvm_interpret_semver | 2 +- 4 files changed, 71 insertions(+), 118 deletions(-) delete mode 100644 semver.md diff --git a/nvm.sh b/nvm.sh index 31faaee3f3..14f2f9304e 100644 --- a/nvm.sh +++ b/nvm.sh @@ -478,11 +478,7 @@ nvm_validate_semver() { ( nvm_interpret_complex_semver() { ( semver="${1-}" version_list="${2-}" # expected to be sorted from oldest to newest - if [ -z "$semver" ] || [ -z "$version_list" ]; then - nvm_err "Error interpretting complex semver: Missing required parameter(s)" - return 1 - elif ! nvm_is_valid_semver "$semver"; then - nvm_err "Error interpretting complex semver: Given an invalid semver" + if [ -z "$semver" ] || [ -z "$version_list" ] || ! nvm_is_valid_semver "$semver"; then return 1 fi @@ -499,18 +495,14 @@ nvm_interpret_complex_semver() { ( semver=$(command printf "%s" "$semver" | command tail -n +2) [ -n "$current_comparator_set" ] || continue - is_current_version_compatible=1 # initialized to false - - # For each version in the version_list_copy (from newest to oldest): - # - If current_version satisfies all comparators, we've found the newest version compatible with all comparators in current current_comparator_set. - # - Add discovered version to highest_compatible_versions and stop iterating through versions. - while [ -n "$version_list_copy" ] && [ $is_current_version_compatible -eq 1 ]; do + # For each version in the version_list_copy (iterating from newest to oldest): + # - If current_version satisfies all comparators in current_comparator_set, we've found the newest version compatible with all comparators in current current_comparator_set. + # - Add discovered version to highest_compatible_versions and stop iterating through versions for current_comparator_set. + while [ -n "$version_list_copy" ]; do current_version=$(command printf "%s" "$version_list_copy" | command tail -n1 | command sed -E 's/^ +//;s/ +$//' | nvm_grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+$') version_list_copy=$(command printf "%s" "$version_list_copy" | command sed '$d') [ -n "$current_version" ] || continue - is_current_version_compatible=1 # initialized to false - # For each comparator in the current_comparator_set_copy: # - If current_version is compatible with all comparators, we know current_version is the newest compatible version current_comparator_set_copy=$(command printf "%s" "$current_comparator_set" | command tr ' ' '\n') @@ -520,67 +512,86 @@ nvm_interpret_complex_semver() { ( [ -n "$current_comparator" ] || continue stripped_version_from_comparator="$(command printf "%s" "$current_comparator" | nvm_grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+$')" + if [ -z "$stripped_version_from_comparator" ]; then + return 1 + fi if nvm_string_contains_regexp "$current_comparator" '^[0-9]+\.[0-9]+\.[0-9]+$'; then - if [ "$current_comparator" = "$current_version" ]; then - # current_omparator is looking for an exact match, and the current_version is the exact match, so this current_comparator is satisfied. - is_current_version_compatible=0 - elif nvm_version_greater "$current_comparator" "$current_version"; then - # Looking for a version that is equal to current_comparator but current_version is less than current_comparator so there is no point continuing. - is_current_version_compatible=1 - no_remote_version_will_satisfy_all_comparators=0 + if [ "$stripped_version_from_comparator" = "$current_version" ]; then + # current_comparator is looking for an exact match, and the current_version is the exact match, so this current_comparator is satisfied. + if [ -z "$current_comparator_set_copy" ]; then + # Also, this is the last comparator in the current_comparator_set_copy so we can assume we've found the newest compatible version of the current_comparator_set. + highest_compatible_versions="$highest_compatible_versions $current_version" + version_list_copy='' + fi + elif nvm_version_greater "$stripped_version_from_comparator" "$current_version"; then + # current_version is less than the stripped_version_from_comparator, so continuing iterating through version_list_copy is pointless. + current_comparator_set_copy='' + version_list_copy='' else - is_current_version_compatible=1 + # current_version is greater than the stripped_version_from_comparator, so we should continuing iterating through version_list_copy. + current_comparator_set_copy='' fi elif nvm_string_contains_regexp "$current_comparator" '^<=[0-9]+\.[0-9]+\.[0-9]+$'; then if nvm_version_greater_than_or_equal_to "$stripped_version_from_comparator" "$current_version"; then - # current_version is less and or equal to the current_comparator version number so this current_comparator is satisfied. - is_current_version_compatible=0 + # current_version is less than or equal to the current_comparator version number so this current_comparator is satisfied. + if [ -z "$current_comparator_set_copy" ]; then + # Also, this is the last comparator in the current_comparator_set_copy so we can assume we've found the newest compatible version of the current_comparator_set. + highest_compatible_versions="$highest_compatible_versions $current_version" + version_list_copy='' + fi else - is_current_version_compatible=1 + # current_version is greater than the current_comparator version number so we should continue iterating through version_list_copy. + current_comparator_set_copy='' fi elif nvm_string_contains_regexp "$current_comparator" '^>=[0-9]+\.[0-9]+\.[0-9]+$'; then if nvm_version_greater_than_or_equal_to "$current_version" "$stripped_version_from_comparator"; then - is_current_version_compatible=0 + # current_version is greater than or equal to the current_comparator version number so this current_comparator is satisfied. + if [ -z "$current_comparator_set_copy" ]; then + # Also, this is the last comparator in the current_comparator_set_copy so we can assume we've found the newest compatible version of the current_comparator_set. + highest_compatible_versions="$highest_compatible_versions $current_version" + version_list_copy='' + fi else - is_current_version_compatible=1 - no_remote_version_will_satisfy_all_comparators=0 + # current_version is less than the current_comparator version number so continuing iterating through version_list_copy is pointless. + current_comparator_set_copy='' + version_list_copy='' fi elif nvm_string_contains_regexp "$current_comparator" '^<[0-9]+\.[0-9]+\.[0-9]+$'; then if nvm_version_greater "$stripped_version_from_comparator" "$current_version"; then - is_current_version_compatible=0 + # current_version is less than the current_comparator version number so this current_comparator is satisfied. + if [ -z "$current_comparator_set_copy" ]; then + # Also, this is the last comparator in the current_comparator_set_copy so we can assume we've found the newest compatible version of the current_comparator_set. + highest_compatible_versions="$highest_compatible_versions $current_version" + version_list_copy='' + fi else - is_current_version_compatible=1 + # current_version is greater than or equal to the current_comparator version number so we should continue iterating through version_list_copy. + current_comparator_set_copy='' fi elif nvm_string_contains_regexp "$current_comparator" '^>[0-9]+\.[0-9]+\.[0-9]+$'; then if nvm_version_greater "$current_version" "$stripped_version_from_comparator"; then - is_current_version_compatible=0 + # current_version is greater than the current_comparator version number so this current_comparator is satisfied. + if [ -z "$current_comparator_set_copy" ];then + # Also, this is the last comparator in the current_comparator_set_copy so we can assume we've found the newest compatible version of the current_comparator_set. + highest_compatible_versions="$highest_compatible_versions $current_version" + version_list_copy='' + fi else - is_current_version_compatible=1 - no_remote_version_will_satisfy_all_comparators=0 + # current_version is less than or equal to the current_comparator version number so continuing iterating through version_list_copy is pointless. + current_comparator_set_copy='' + version_list_copy='' fi else - is_current_version_compatible=1 - fi - # If current_version is not compatible with current_comparator, stop iterating through current_comparator_set_copy. - if [ $is_current_version_compatible -eq 1 ]; then current_comparator_set_copy='' fi - # If determined that continuing iterating through version_list_copy will not find a version compatible with all comparators, stop iterating through version_list_copy. - if [ ${no_remote_version_will_satisfy_all_comparators-1} -eq 0 ]; then - version_list_copy='' - fi - done # while [ -n "$current_comparator_set_copy" ] && [ $no_remote_version_will_satisfy_all_comparators -eq 1 ]; do - # If the current_version is compatible with all comparators in current_comparator_set, add it to the list of highest_compatible_versions. - if [ $is_current_version_compatible -eq 0 ]; then - highest_compatible_versions="$highest_compatible_versions $current_version" - fi - done # while [ -n "$version_list_copy" ] && [ $is_current_version_compatible -eq 1 ]; do + done # while [ -n "$current_comparator_set_copy" ]; do + done # while [ -n "$version_list_copy" ]; do done # while [ -n "$semver" ]; do # Iterate through each of the versions in highest_compatible_versions, which are the highest versions that satisfy each of the comparator sets. @@ -607,18 +618,12 @@ nvm_interpret_complex_semver() { ( nvm_interpret_simple_semver() { ( semver="${1-}" version_list="${2-}" # expected to be sorted from oldest to newest - if [ -z "$semver" ] || [ -z "$version_list" ]; then - nvm_err "Error interpretting simple semver: Missing required parameter(s)" - return 1 - fi - if ! nvm_string_contains_regexp "$semver" '^[<>=]*[0-9]+\.[0-9]+\.[0-9]+$'; then - # semver is not a semver with only a single comparator + if [ -z "$semver" ] || [ -z "$version_list" ] || ! nvm_string_contains_regexp "$semver" '^[<>=]*[0-9]+\.[0-9]+\.[0-9]+$'; then return 1 fi stripped_version_from_semver="$(command printf "%s" "$semver" | nvm_grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+$')" newest_version_from_list=$(command printf "%s" "$version_list" | tail -n 1 | nvm_grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+$') - if [ -z "$stripped_version_from_semver" ]; then - nvm_err "Error interpretting simple semver: error retrieving stripped version from semver" + if [ -z "$stripped_version_from_semver" ] || [ -z "$newest_version_from_list" ]; then return 1 fi # if the semver is looking for an exact match, and it exists in the provided list of versions, resolve to that version @@ -627,6 +632,7 @@ nvm_interpret_simple_semver() { ( command printf "%s" "$stripped_version_from_semver" return 0 else + # TODO we know it's not worth doing the complex semver interpratation at this point return 1 fi @@ -645,6 +651,7 @@ nvm_interpret_simple_semver() { ( command printf "%s" "$newest_version_from_list" return 0 else + # TODO we know it's not worth doing the complex semver interpretation at this point return 1 fi @@ -653,6 +660,7 @@ nvm_interpret_simple_semver() { ( command printf "%s" "$newest_version_from_list" return 0 else + # TODO we know it's not worth doing the complex semver interpretation at this point return 1 fi @@ -663,28 +671,25 @@ nvm_interpret_simple_semver() { ( # Given a semantic version, resolve it to the newest compatible remote node version. # NOTE: Surrounding the function body in parens limits the scope of variables in this function to only this function. -nvm_interpret_semver() { ( +nvm_interpret_node_semver() { ( semver="${1-}" if [ -z "$semver" ]; then - nvm_err "Error interpretting semver: Missing semver parameter" return 1 fi # Validate incoming semver and transform it into the grammar that is expected by the following logic valid_transformed_semver=$(nvm_validate_semver "$semver") if [ -z "$valid_transformed_semver" ]; then - nvm_err "Error interpretting semver: invalid semver: '$semver'" return 1 fi - # TODO add tests verifying this logic correctly gets list of node versions and that the order is from oldest to newest # list of node versions is sorted from oldest to newest remote_node_versions=$(nvm_ls_remote | nvm_grep -o 'v[0-9]\+\.[0-9]\+\.[0-9]\+') if [ -z "$remote_node_versions" ]; then - nvm_err "Error interpretting semver: failure retrieving remote node versions" return 1 fi + # TODO update nvm_interpret_simple_semver failure output to indicate if it is worth doing complex semver interpretation # If semver is a single comparator, use quick algorithm to determine newest compatible version resolved_version=$(nvm_interpret_simple_semver "$valid_transformed_semver" "$remote_node_versions") if [ -n "$resolved_version" ]; then @@ -774,7 +779,7 @@ nvm_package_json_version() { else nvm_echo "Found '${pkg_json_path}' with semver expression <${pkg_json_semver}>" # attempt complex semver range evaluation - RESOLVED_PKG_JSON_VERSION=$(nvm_interpret_semver "$pkg_json_semver") + RESOLVED_PKG_JSON_VERSION=$(nvm_interpret_node_semver "$pkg_json_semver") if [ ! -n "${RESOLVED_PKG_JSON_VERSION}" ]; then nvm_err "Warning: could not interpret engines.node semver expression obtained from package.json file." return 2 @@ -4040,7 +4045,7 @@ nvm() { node_version_has_solaris_binary iojs_version_has_solaris_binary \ nvm_curl_libz_support nvm_command_info \ nvm_get_node_from_pkg_json nvm_find_package_json nvm_package_json_version \ - nvm_interpret_semver nvm_interpret_simple_semver nvm_interpret_complex_semver nvm_validate_semver \ + nvm_interpret_node_semver nvm_interpret_simple_semver nvm_interpret_complex_semver nvm_validate_semver \ nvm_is_valid_semver nvm_string_contains_regexp \ > /dev/null 2>&1 unset NVM_RC_VERSION NVM_NODEJS_ORG_MIRROR NVM_IOJS_ORG_MIRROR NVM_DIR \ diff --git a/semver.md b/semver.md deleted file mode 100644 index bb4d68ce4e..0000000000 --- a/semver.md +++ /dev/null @@ -1,52 +0,0 @@ -# Semantic version interpretation documentation - -Node versions are interpretted from the semver expression located in the engines.node value of the local package.json file. The algorithm for interpretting the semver is expressed at a high level below and is intended to work with the [semantic versioner for npm](https://docs.npmjs.com/misc/semver). - -## 1. Convert the semver into the following grammar: - -> ``` -> semver ::= comparator_set ( ' || ' comparator_set )* -> comparator_set ::= comparator ( ' ' comparator )* -> comparator ::= ( '<' | '<=' | '>' | '>=' | '' ) [0-9]+ '.' [0-9]+ '.' [0-9]+ -> ``` - -## 2. Resolve each comparator set to its newest compatible node version - -**First, if semver only contains a single comparator_set, we may be able to quickly find the newest compatible version.** - -```pseudocode -if semver is looking for an exact match of some specified version and the specified version is a valid node version - resolve to the specified version -else if semver is looking for a version less than or equal to some specified version and the specified version is a valid node version - resolve to the specified version -else if semver is looking for a version greater than or equal to some specified version and the current newest node version is greater than or equal to the specified version - resolve to the current newest node version -else if semver is looking for a version strictly greater than to some specified version and the current newest node version is greater than the specified version - resolve to the current newest node version -else - quick resolution of semver interpretation not possible -``` - -**If quick resolution of semver interpretation does not work, try more complex semver interpretation algorithm.** - -```pseudocode -initialize highest_compatible_versions to an empty list -initialize node_version_list to the list of current remote node versions -for each current_comparator_set in the semver { - for each current_node_version in node_version_list { - for each current_comparator in current_comparator_set { - if current_node_version is compatible with current_comparator - continue seeing if current_node_version might be compatible with all comparators in current_comparator_set - else if current_node_version is not compatible with current_comparator - if it can be determined that no older version will satisfy this comparator, we can move on to the next comparator_set in the semver - else - stop seeing if current_node_version is compatible with all comparators in current_comparator_set and move on to the next version - } - if current_node_version was found to be compatible with all comparators in current_comparator_set - add current_node_version to the highest_compatible_versions list - } -} - -resolve to the highest version among all the versions collected in highest_compatible_versions -``` - diff --git a/test/fast/Unit tests/nvm_get_node_from_pkg_json b/test/fast/Unit tests/nvm_get_node_from_pkg_json index c9e2f01832..fee0f9f225 100755 --- a/test/fast/Unit tests/nvm_get_node_from_pkg_json +++ b/test/fast/Unit tests/nvm_get_node_from_pkg_json @@ -131,7 +131,7 @@ for TEMPLATE_NAME in package_json_templates/_valid_*; do TEST_SEMVER_INPUT=$(echo "$LINE" | awk -F: '{ print $1 }') EXPECTED_OUTPUT=$(echo "$LINE" | awk -F: '{ print $2 }') - if [ "$PREV_TEST_SEMVER" == "$TEST_SEMVER_INPUT" ]; then + if [ "$PREV_TEST_SEMVER" = "$TEST_SEMVER_INPUT" ]; then die "Problem iterating through TEST_SEMVERS_COPY (TEST SET #1). Encountered the same value twice in a row. PREV_TEST_SEMVER='$PREV_TEST_SEMVER' TEST_SEMVER_INPUT='$TEST_SEMVER_INPUT'.\n" fi PREV_TEST_SEMVER=$(printf "%s" "$TEST_SEMVER_INPUT") @@ -159,7 +159,7 @@ for TEMPLATE_NAME in package_json_templates/_invalid_*; do [ -n "$LINE" ] || continue TEST_SEMVER_INPUT=$(echo "$LINE" | awk -F: '{ print $1 }') - if [ "$PREV_TEST_SEMVER" == "$TEST_SEMVER_INPUT" ]; then + if [ "$PREV_TEST_SEMVER" = "$TEST_SEMVER_INPUT" ]; then die "Problem iterating through TEST_SEMVERS_COPY (TEST SET #2). Encountered the same value twice in a row. PREV_TEST_SEMVER='$PREV_TEST_SEMVER' TEST_SEMVER_INPUT='$TEST_SEMVER_INPUT'.\n" fi PREV_TEST_SEMVER=$(printf "%s" "$TEST_SEMVER_INPUT") @@ -201,7 +201,7 @@ for TEMPLATE_NAME in package_json_templates/_valid_*; do [ -n "$TEST_SEMVER_INPUT" ] || continue TEST_SEMVERS_COPY=$(echo "$TEST_SEMVERS_COPY" | tail -n +2) - if [ "$PREV_TEST_SEMVER" == "$TEST_SEMVER_INPUT" ]; then + if [ "$PREV_TEST_SEMVER" = "$TEST_SEMVER_INPUT" ]; then die "Problem iterating through TEST_SEMVERS_COPY (TEST SET #3). Encountered the same value twice in a row. PREV_TEST_SEMVER='$PREV_TEST_SEMVER' TEST_SEMVER_INPUT='$TEST_SEMVER_INPUT'.\n" fi PREV_TEST_SEMVER=$(printf "%s" "$TEST_SEMVER_INPUT") @@ -224,7 +224,7 @@ for TEMPLATE_NAME in package_json_templates/_invalid_*; do [ -n "$TEST_SEMVER_INPUT" ] || continue TEST_SEMVERS_COPY=$(echo "$TEST_SEMVERS_COPY" | tail -n +2) - if [ "$PREV_TEST_SEMVER" == "$TEST_SEMVER_INPUT" ]; then + if [ "$PREV_TEST_SEMVER" = "$TEST_SEMVER_INPUT" ]; then die "Problem iterating through TEST_SEMVERS_COPY (TEST SET #4). Encountered the same value twice in a row. PREV_TEST_SEMVER='$PREV_TEST_SEMVER' TEST_SEMVER_INPUT='$TEST_SEMVER_INPUT'.\n" fi PREV_TEST_SEMVER=$(printf "%s" "$TEST_SEMVER_INPUT") diff --git a/test/fast/Unit tests/nvm_interpret_semver b/test/fast/Unit tests/nvm_interpret_semver index e7d5a6431b..b853b397e9 100755 --- a/test/fast/Unit tests/nvm_interpret_semver +++ b/test/fast/Unit tests/nvm_interpret_semver @@ -29,7 +29,7 @@ while [ -n "$test_cases" ]; do LINE=$(echo "$test_cases" | head -n1) INPUT=$(echo "$LINE" | awk -F: '{ print $1 }') EXPECTED_OUTPUT=$(echo "$LINE" | awk -F: '{ print $2 }') - ACTUAL_OUTPUT=$(nvm_interpret_semver "$INPUT") + ACTUAL_OUTPUT=$(nvm_interpret_node_semver "$INPUT") if [ "$ACTUAL_OUTPUT" != "$EXPECTED_OUTPUT" ] || [ -z "$INPUT" ]; then die "Expected output: '$EXPECTED_OUTPUT'. Actual output: '$ACTUAL_OUTPUT'. Input: '$INPUT'.\n" fi From 777f11e95c877b6fa88999fa17b21e72a95d9a20 Mon Sep 17 00:00:00 2001 From: edwmurph Date: Tue, 29 May 2018 08:40:52 -0400 Subject: [PATCH 06/12] localized variables in functions; refactored sed in nvm_validate_semver --- nvm.sh | 165 +++++++++++++---------- test/fast/Unit tests/nvm_is_valid_semver | 41 ++++++ 2 files changed, 134 insertions(+), 72 deletions(-) create mode 100755 test/fast/Unit tests/nvm_is_valid_semver diff --git a/nvm.sh b/nvm.sh index 14f2f9304e..c57d43d736 100644 --- a/nvm.sh +++ b/nvm.sh @@ -299,16 +299,17 @@ nvm_find_up() { nvm_echo "${path_}" } -# NOTE: Surrounding the function body in parens limits the scope of variables in this function to only this function. -nvm_string_contains_regexp() { ( +nvm_string_contains_regexp() { + local string string="${1-}" + local regexp regexp="${2-}" if [ -z "$string" ] || [ -z "$regexp" ]; then return 1 fi # e.g. "nvm_string_contains_regexp abbc ^aa?b+.$" returns 0 command printf "%s" "$string" | command awk "/$regexp/{ exit 0 }{ exit 1 }" -) } +} # Validates that the given semver adheres to the following grammar: # @@ -328,67 +329,70 @@ nvm_is_valid_semver() { # semver ::= comparator_set ( ' || ' comparator_set )* # comparator_set ::= comparator ( ' ' comparator )* # comparator ::= ( '<' | '<=' | '>' | '>=' | '' ) [0-9]+ '.' [0-9]+ '.' [0-9]+ -# -# NOTE: Surrounding the function body in parens limits the scope of variables in this function to only this function. -nvm_validate_semver() { ( +nvm_validate_semver() { # split the semantic version into comparator_set's + local semver semver=$(command printf "%s" "${1-}" | command tr '||' '\n') if [ -z "$semver" ]; then return 1 fi + local validated_semver validated_semver=''; while [ -n "$semver" ]; do + local comparator_set comparator_set=$(command printf "%s" "$semver" | command head -n1) semver=$(command printf "%s" "$semver" | command tail -n +2) [ -n "$comparator_set" ] || continue # convert comparators into required grammar - validated_comparator_set=$(command printf " %s " "$comparator_set" | + local validated_comparator_set + validated_comparator_set=$(command printf " %s " "$comparator_set" \ + | command sed -E " # exactly 1 space is needed before and after every comparator (including the first and last comparators) - command sed -E 's/\011/ /g;s/ +/ /g' | - - # normalize all wildcards to `x` - command sed -E 's/X|\*/x/g' | - - # ` 1 ` => ` 1.x.x ` - # ` x ` => ` x.x.x ` - command sed -E 's/ ([0-9]+|x) / \1.x.x /g' | - - # ` 1.2 ` => ` 1.2.x ` - # ` 1.x ` => ` 1.x.x ` - # ` x.x ` => ` x.x.x ` - command sed -E 's/ (([0-9]+|x)\.([0-9]+|x)) / \1.x /g' | - - # ` 1.2.3 - 1.2.4 ` => ` >=1.2.3 <=1.2.4 ` - command sed -E 's/ (([0-9]+|x)\.([0-9]+|x)\.([0-9]+|x)) ?\- ?(([0-9]+|x)\.([0-9]+|x)\.([0-9]+|x)) / >=\1 <=\5 /g' | - - # ` > 1.2.3 ` => ` >1.2.3 ` - # ` < 1.2.5 ` => ` <1.2.5 ` - # ` <= 1.2.3 ` => ` <=1.2.3 ` - # ` >= 1.2.3 ` => ` >=1.2.3 ` - # ` = 1.2.3 ` => ` =1.2.3 ` - # ` ~ 1.2.3 ` => ` ~1.2.3 ` - # ` ^ 1.2.3 ` => ` ^1.2.3 ` - command sed -E 's/ (<|>|<=|>=|=|~|\^) (([0-9]+|x)\.([0-9]+|x)\.([0-9]+|x)) / \1\2 /g' | - - # ` =1.2.3 ` => ` 1.2.3 ` - command sed -E 's/ =//g' | - - # handle conversions of comparators with '^' or '~' or 'x' into required grammar - # ` ^0.0.1 ` => ` >=0.0.1 <0.0.2 ` - # ` ^0.1.2 ` => ` >=0.1.2 <0.2.0 ` - # ` ^1.2.3 ` => ` >=1.2.3 <2.0.0 ` - # ` ~0.0.1 ` => ` >=0.0.1 <0.1.0 ` - # ` ~0.1.2 ` => ` >=0.1.2 <0.2.0 ` - # ` ~1.2.3 ` => ` >=1.2.3 <1.3.0 ` - # ` x.x.x ` => ` >0.0.0 ` - # ` x.x.1 ` => ` >0.0.0 ` - # ` x.1.x ` => ` >0.0.0 ` - # ` x.1.2 ` => ` >0.0.0 ` - # ` 1.2.x ` => ` >=1.2.0 <1.3.0 ` - # ` 1.x.1 ` => ` >=1.0.0 <2.0.0 ` NOTE the last "1" is ignored in this grammar - # ` 1.x.x ` => ` >=1.0.0 <2.0.0 ` - command awk '{ + s/\011/ /g;s/ +/ /g; + + # normalize all wildcards to x + s/X|\*/x/g; + + # ' 1 ' => ' 1.x.x ' + # ' x ' => ' x.x.x ' + s/ ([0-9]+|x) / \1.x.x /g; + + # ' 1.2 ' => ' 1.2.x ' + # ' 1.x ' => ' 1.x.x ' + # ' x.x ' => ' x.x.x ' + s/ (([0-9]+|x)\.([0-9]+|x)) / \1.x /g; + + # ' 1.2.3 - 1.2.4 ' => ' >=1.2.3 <=1.2.4 ' + s/ (([0-9]+|x)\.([0-9]+|x)\.([0-9]+|x)) ?\- ?(([0-9]+|x)\.([0-9]+|x)\.([0-9]+|x)) / >=\1 <=\5 /g; + + # ' > 1.2.3 ' => ' >1.2.3 ' + # ' < 1.2.5 ' => ' <1.2.5 ' + # ' <= 1.2.3 ' => ' <=1.2.3 ' + # ' >= 1.2.3 ' => ' >=1.2.3 ' + # ' = 1.2.3 ' => ' =1.2.3 ' + # ' ~ 1.2.3 ' => ' ~1.2.3 ' + # ' ^ 1.2.3 ' => ' ^1.2.3 ' + s/ (<|>|<=|>=|=|~|\^) (([0-9]+|x)\.([0-9]+|x)\.([0-9]+|x)) / \1\2 /g; + + # ' =1.2.3 ' => ' 1.2.3 ' + s/ =//g; + " \ + | command awk '{ + # handle conversions of comparators with '^' or '~' or 'x' into required grammar + # ` ^0.0.1 ` => ` >=0.0.1 <0.0.2 ` + # ` ^0.1.2 ` => ` >=0.1.2 <0.2.0 ` + # ` ^1.2.3 ` => ` >=1.2.3 <2.0.0 ` + # ` ~0.0.1 ` => ` >=0.0.1 <0.1.0 ` + # ` ~0.1.2 ` => ` >=0.1.2 <0.2.0 ` + # ` ~1.2.3 ` => ` >=1.2.3 <1.3.0 ` + # ` x.x.x ` => ` >0.0.0 ` + # ` x.x.1 ` => ` >0.0.0 ` + # ` x.1.x ` => ` >0.0.0 ` + # ` x.1.2 ` => ` >0.0.0 ` + # ` 1.2.x ` => ` >=1.2.0 <1.3.0 ` + # ` 1.x.1 ` => ` >=1.0.0 <2.0.0 ` NOTE the last "1" is ignored in this grammar + # ` 1.x.x ` => ` >=1.0.0 <2.0.0 ` if ( ! match($0, /[\^~x]/) ) { print $0 exit 0 @@ -448,10 +452,8 @@ nvm_validate_semver() { ( } } print output - }' | - - # remove leading/trailing spaces - command sed -E 's/^ +//;s/ +$//' + }' \ + | command sed -E 's/^ +//;s/ +$//' ) # only comparator_sets composed of the required grammar are marked as valid @@ -469,14 +471,15 @@ nvm_validate_semver() { ( else return 1 fi -) } +} # Given a semver and version list, find the highest compatible version by doing the following: # - Find the newest compatible version of each comparator set. # - Resolve to the newest of all the newest compatible versions of each comparator set. -# NOTE: Surrounding the function body in parens limits the scope of variables in this function to only this function. -nvm_interpret_complex_semver() { ( +nvm_interpret_complex_semver() { + local semver semver="${1-}" + local version_list version_list="${2-}" # expected to be sorted from oldest to newest if [ -z "$semver" ] || [ -z "$version_list" ] || ! nvm_is_valid_semver "$semver"; then return 1 @@ -487,10 +490,13 @@ nvm_interpret_complex_semver() { ( # - Add the discovered newest compatible version to highest_compatible_versions. # - Choose the highest version among all the versions collected in highest_compatible_versions. semver=$(command printf "%s" "$semver" | command tr '||' '\n') + local highest_compatible_versions highest_compatible_versions='' while [ -n "$semver" ]; do + local version_list_copy version_list_copy=$(command printf "%s" "$version_list") + local current_comparator_set current_comparator_set=$(command printf "%s" "$semver" | command head -n1 | command sed -E 's/^ +//;s/ +$//') semver=$(command printf "%s" "$semver" | command tail -n +2) [ -n "$current_comparator_set" ] || continue @@ -499,18 +505,22 @@ nvm_interpret_complex_semver() { ( # - If current_version satisfies all comparators in current_comparator_set, we've found the newest version compatible with all comparators in current current_comparator_set. # - Add discovered version to highest_compatible_versions and stop iterating through versions for current_comparator_set. while [ -n "$version_list_copy" ]; do + local current_version current_version=$(command printf "%s" "$version_list_copy" | command tail -n1 | command sed -E 's/^ +//;s/ +$//' | nvm_grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+$') version_list_copy=$(command printf "%s" "$version_list_copy" | command sed '$d') [ -n "$current_version" ] || continue # For each comparator in the current_comparator_set_copy: # - If current_version is compatible with all comparators, we know current_version is the newest compatible version + local current_comparator_set_copy current_comparator_set_copy=$(command printf "%s" "$current_comparator_set" | command tr ' ' '\n') while [ -n "$current_comparator_set_copy" ]; do + local current_comparator current_comparator=$(command printf "%s" "$current_comparator_set_copy" | command head -n1 | command sed -E 's/^ +//;s/ +$//') current_comparator_set_copy=$(command printf "%s" "$current_comparator_set_copy" | command tail -n +2) [ -n "$current_comparator" ] || continue + local stripped_version_from_comparator stripped_version_from_comparator="$(command printf "%s" "$current_comparator" | nvm_grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+$')" if [ -z "$stripped_version_from_comparator" ]; then return 1 @@ -597,9 +607,11 @@ nvm_interpret_complex_semver() { ( # Iterate through each of the versions in highest_compatible_versions, which are the highest versions that satisfy each of the comparator sets. # Since comparator sets are separated by '||', choosing any of the highest versions compatible with any of the comparator_sets would be compatible with the whole semver. # Therefore, we should resolve to the highest version in highest_compatible_versions. + local highest_compatible_version highest_compatible_version='0.0.0' highest_compatible_versions=$(command printf "%s" "$highest_compatible_versions" | command tr ' ' '\n') while [ -n "$highest_compatible_versions" ]; do + local compatible_node_version compatible_node_version=$(command printf "%s" "$highest_compatible_versions" | command head -n1 | command sed -E 's/^ +//;s/ +$//') highest_compatible_versions=$(command printf "%s" "$highest_compatible_versions" | command tail -n +2) [ -n "$compatible_node_version" ] || continue @@ -611,17 +623,20 @@ nvm_interpret_complex_semver() { ( if [ "$highest_compatible_version" != '0.0.0' ]; then command printf "%s" "$highest_compatible_version" fi -) } +} # Given a semver and version list, optimize discovery of highest compatible version with this function which quickly interprets some common semvers. -# NOTE: Surrounding the function body in parens limits the scope of variables in this function to only this function. -nvm_interpret_simple_semver() { ( +nvm_interpret_simple_semver() { + local semver semver="${1-}" + local version_list version_list="${2-}" # expected to be sorted from oldest to newest if [ -z "$semver" ] || [ -z "$version_list" ] || ! nvm_string_contains_regexp "$semver" '^[<>=]*[0-9]+\.[0-9]+\.[0-9]+$'; then return 1 fi + local stripped_version_from_semver stripped_version_from_semver="$(command printf "%s" "$semver" | nvm_grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+$')" + local newest_version_from_list newest_version_from_list=$(command printf "%s" "$version_list" | tail -n 1 | nvm_grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+$') if [ -z "$stripped_version_from_semver" ] || [ -z "$newest_version_from_list" ]; then return 1 @@ -667,23 +682,25 @@ nvm_interpret_simple_semver() { ( else return 1 fi -) } +} # Given a semantic version, resolve it to the newest compatible remote node version. -# NOTE: Surrounding the function body in parens limits the scope of variables in this function to only this function. -nvm_interpret_node_semver() { ( +nvm_interpret_node_semver() { + local semver semver="${1-}" if [ -z "$semver" ]; then return 1 fi # Validate incoming semver and transform it into the grammar that is expected by the following logic + local valid_transformed_semver valid_transformed_semver=$(nvm_validate_semver "$semver") if [ -z "$valid_transformed_semver" ]; then return 1 fi # list of node versions is sorted from oldest to newest + local remote_node_versions remote_node_versions=$(nvm_ls_remote | nvm_grep -o 'v[0-9]\+\.[0-9]\+\.[0-9]\+') if [ -z "$remote_node_versions" ]; then return 1 @@ -691,6 +708,7 @@ nvm_interpret_node_semver() { ( # TODO update nvm_interpret_simple_semver failure output to indicate if it is worth doing complex semver interpretation # If semver is a single comparator, use quick algorithm to determine newest compatible version + local resolved_version resolved_version=$(nvm_interpret_simple_semver "$valid_transformed_semver" "$remote_node_versions") if [ -n "$resolved_version" ]; then command printf "%s" "$resolved_version" @@ -705,7 +723,7 @@ nvm_interpret_node_semver() { ( fi return 1 -) } +} nvm_find_package_json() { dir="$(nvm_find_up 'package.json')" @@ -718,13 +736,17 @@ nvm_find_package_json() { # - removes all line breaks and carriage returns # - normalizes all consecutive whitespace to 1 occurrence # - semantic expression must match regexp: "[|<> [:alnum:].^=~*-]\+" -# NOTE: Surrounding the function body in parens limits the scope of variables in this function to only this function. -nvm_get_node_from_pkg_json() { ( +nvm_get_node_from_pkg_json() { + local package_json_contents package_json_contents=${1-} + local engines_node_value engines_node_value='' - open_brackets=0 - closed_brackets=0 - in_quotes=1 + local open_brackets + open_brackets=0 # a counter variable + local closed_brackets + closed_brackets=0 # a counter variable + local in_quotes + in_quotes=1 # a true/false variable command printf "%s" "$package_json_contents" \ | command tr -d '\n\r' \ @@ -761,7 +783,7 @@ nvm_get_node_from_pkg_json() { ( fi done return 2 -) } +} nvm_package_json_version() { export RESOLVED_PKG_JSON_VERSION='' @@ -917,7 +939,6 @@ nvm_version() { local PATTERN PATTERN="${1-}" local VERSION - # The default version is the current one if [ -z "${PATTERN}" ]; then PATTERN='current' diff --git a/test/fast/Unit tests/nvm_is_valid_semver b/test/fast/Unit tests/nvm_is_valid_semver new file mode 100755 index 0000000000..8239251e4a --- /dev/null +++ b/test/fast/Unit tests/nvm_is_valid_semver @@ -0,0 +1,41 @@ +#!/bin/sh + +die () { printf "$@" ; exit 1; } + +\. ../../../nvm.sh + +valid_semver_regexp='^( ?(<|<=|>|>=|=|~|\^)?([0-9]+|x)\.([0-9]+|x)\.([0-9]+|x))+$' + +# POSITIVE TEST CASES + +# STRING:REGEXP +test_cases="1.2.3:$valid_semver_regexp +11.22.33:$valid_semver_regexp" + +while [ -n "$test_cases" ]; do + LINE=$(echo "$test_cases" | head -n1) + STRING=$(echo "$LINE" | awk -F: '{ print $1 }') + REGEXP=$(echo "$LINE" | awk -F: '{ print $2 }') + if [ -z "$REGEXP" ] || [ -z "$STRING" ] || ! nvm_string_contains_regexp "$STRING" "$REGEXP"; then + die "nvm_string_contains_regexp POSITIVE test case failed. REGEXP: '$REGEXP'. STRING: '$STRING'.\n" + fi + test_cases=$(echo "$test_cases" | tail -n +2) +done + +# NEGATIVE TEST CASES + +# STRING:REGEXP +test_cases="1.2.a:$valid_semver_regexp +:$valid_semver_regexp +11.22.a:$valid_semver_regexp" + +while [ -n "$test_cases" ]; do + LINE=$(echo "$test_cases" | head -n1) + STRING=$(echo "$LINE" | awk -F: '{ print $1 }') + REGEXP=$(echo "$LINE" | awk -F: '{ print $2 }') + if [ -z "$REGEXP" ] || nvm_string_contains_regexp "$STRING" "$REGEXP"; then + die "nvm_string_contains_regexp NEGATIVE test case failed. REGEXP: '$REGEXP'. STRING: '$STRING'.\n" + fi + test_cases=$(echo "$test_cases" | tail -n +2) +done +exit 0 From 12f75e415fd7c3e68d70c2164c369804776d002f Mon Sep 17 00:00:00 2001 From: edwmurph Date: Wed, 30 May 2018 17:53:39 -0400 Subject: [PATCH 07/12] significantly increased test coverage; fixed a couple edge cases --- nvm.sh | 51 ++-- .../Unit tests/nvm_get_node_from_pkg_json | 260 +++++------------- .../fast/Unit tests/nvm_interpret_node_semver | 67 +++++ test/fast/Unit tests/nvm_interpret_semver | 38 --- test/fast/Unit tests/nvm_is_valid_semver | 59 ++-- test/fast/Unit tests/nvm_string_equals_regexp | 26 +- ...vm_trim_and_reduce_whitespace_to_one_space | 28 ++ test/fast/Unit tests/nvm_validate_semver | 17 +- .../Unit tests/sharedTestResources/semvers | 187 +++++++++++++ 9 files changed, 438 insertions(+), 295 deletions(-) create mode 100755 test/fast/Unit tests/nvm_interpret_node_semver delete mode 100755 test/fast/Unit tests/nvm_interpret_semver create mode 100755 test/fast/Unit tests/nvm_trim_and_reduce_whitespace_to_one_space create mode 100755 test/fast/Unit tests/sharedTestResources/semvers diff --git a/nvm.sh b/nvm.sh index c57d43d736..fddc9393bd 100644 --- a/nvm.sh +++ b/nvm.sh @@ -299,6 +299,7 @@ nvm_find_up() { nvm_echo "${path_}" } +# NOTE: this function only validates across one line nvm_string_contains_regexp() { local string string="${1-}" @@ -324,6 +325,16 @@ nvm_is_valid_semver() { fi } +# TODO figure out if the commented out logic is needed anywhere +nvm_trim_and_reduce_whitespace_to_one_space() { + command printf "%s" "${1-}" | + command tr -d '\n\r' | + command tr '\t' ' ' | + command tr -s ' ' | + command sed 's/^ //; s/ $//; s/^ //' +} + +# TODO rename this function to 'nvm_normalize_semver' # Attempts to convert given semver to the following grammar: # # semver ::= comparator_set ( ' || ' comparator_set )* @@ -332,7 +343,7 @@ nvm_is_valid_semver() { nvm_validate_semver() { # split the semantic version into comparator_set's local semver - semver=$(command printf "%s" "${1-}" | command tr '||' '\n') + semver=$(nvm_trim_and_reduce_whitespace_to_one_space "${1-}" | command tr '||' '\n') if [ -z "$semver" ]; then return 1 fi @@ -354,6 +365,9 @@ nvm_validate_semver() { # normalize all wildcards to x s/X|\*/x/g; + # space out numbers surrounding '-' + s/ ?- ?/ - /g; + # ' 1 ' => ' 1.x.x ' # ' x ' => ' x.x.x ' s/ ([0-9]+|x) / \1.x.x /g; @@ -373,10 +387,12 @@ nvm_validate_semver() { # ' = 1.2.3 ' => ' =1.2.3 ' # ' ~ 1.2.3 ' => ' ~1.2.3 ' # ' ^ 1.2.3 ' => ' ^1.2.3 ' - s/ (<|>|<=|>=|=|~|\^) (([0-9]+|x)\.([0-9]+|x)\.([0-9]+|x)) / \1\2 /g; + # ' v 1.2.3 ' => ' v1.2.3 ' + s/ (v|<|>|<=|>=|=|~|\^) (([0-9]+|x)\.([0-9]+|x)\.([0-9]+|x)) / \1\2 /g; # ' =1.2.3 ' => ' 1.2.3 ' - s/ =//g; + # ' v1.2.3 ' => ' 1.2.3 ' + s/ (=|v)//g; " \ | command awk '{ # handle conversions of comparators with '^' or '~' or 'x' into required grammar @@ -506,7 +522,7 @@ nvm_interpret_complex_semver() { # - Add discovered version to highest_compatible_versions and stop iterating through versions for current_comparator_set. while [ -n "$version_list_copy" ]; do local current_version - current_version=$(command printf "%s" "$version_list_copy" | command tail -n1 | command sed -E 's/^ +//;s/ +$//' | nvm_grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+$') + current_version=$(command printf "%s" "$version_list_copy" | command tail -n1 | command sed -E 's/^ +//;s/ +$//' | nvm_grep -o '^[0-9]\+\.[0-9]\+\.[0-9]\+$') version_list_copy=$(command printf "%s" "$version_list_copy" | command sed '$d') [ -n "$current_version" ] || continue @@ -635,16 +651,18 @@ nvm_interpret_simple_semver() { return 1 fi local stripped_version_from_semver - stripped_version_from_semver="$(command printf "%s" "$semver" | nvm_grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+$')" + stripped_version_from_semver="$(command printf "%s" "$semver" | nvm_grep -o '^[0-9]\+\.[0-9]\+\.[0-9]\+$')" local newest_version_from_list - newest_version_from_list=$(command printf "%s" "$version_list" | tail -n 1 | nvm_grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+$') + newest_version_from_list=$(command printf "%s" "$version_list" | tail -n 1 | nvm_grep -o '^[0-9]\+\.[0-9]\+\.[0-9]\+$') if [ -z "$stripped_version_from_semver" ] || [ -z "$newest_version_from_list" ]; then return 1 fi + local retrieved_version # if the semver is looking for an exact match, and it exists in the provided list of versions, resolve to that version if nvm_string_contains_regexp "$semver" '^[0-9]+\.[0-9]+\.[0-9]+$'; then - if nvm_string_contains_regexp "$version_list" "^v$stripped_version_from_semver$"; then - command printf "%s" "$stripped_version_from_semver" + retrieved_version=$(command printf "%s" "$version_list" | nvm_grep "^$stripped_version_from_semver$") + if [ -n "$retrieved_version" ]; then + command printf "%s" "$retrieved_version" return 0 else # TODO we know it's not worth doing the complex semver interpratation at this point @@ -653,11 +671,12 @@ nvm_interpret_simple_semver() { # Semver is looking for the newest version that is <= to a sepcific version, and the version exists in the provided list of versions, resolve to that version elif nvm_string_contains_regexp "$semver" '^<=[0-9]+\.[0-9]+\.[0-9]+$'; then - if nvm_string_contains_regexp "$version_list" "^v$stripped_version_from_semver$"; then - command printf "%s" "$stripped_version_from_semver" + retrieved_version=$(command printf "%s" "$version_list" | nvm_grep "^$stripped_version_from_semver$") + if [ -n "$retrieved_version" ]; then + command printf "%s" "$retrieved_version" return 0 else - return 1 + return 1 # go on to try complex semver interpretation fi # Semver is looking for the newest version >= a specific version, and the newest version in the provided list of versions is >= the specified version, resolve to that version. @@ -701,7 +720,7 @@ nvm_interpret_node_semver() { # list of node versions is sorted from oldest to newest local remote_node_versions - remote_node_versions=$(nvm_ls_remote | nvm_grep -o 'v[0-9]\+\.[0-9]\+\.[0-9]\+') + remote_node_versions=$(nvm_ls_remote | nvm_grep -o 'v[0-9]\+\.[0-9]\+\.[0-9]\+' | sed 's/^v//g') if [ -z "$remote_node_versions" ]; then return 1 fi @@ -747,11 +766,7 @@ nvm_get_node_from_pkg_json() { closed_brackets=0 # a counter variable local in_quotes in_quotes=1 # a true/false variable - - command printf "%s" "$package_json_contents" \ - | command tr -d '\n\r' \ - | command tr '\t' ' ' \ - | command tr -s ' ' \ + nvm_trim_and_reduce_whitespace_to_one_space "$package_json_contents" \ | nvm_grep -o '"engines": \?{ \?".*' \ | nvm_grep -o '{.*' \ | nvm_grep -o . \ @@ -4067,7 +4082,7 @@ nvm() { nvm_curl_libz_support nvm_command_info \ nvm_get_node_from_pkg_json nvm_find_package_json nvm_package_json_version \ nvm_interpret_node_semver nvm_interpret_simple_semver nvm_interpret_complex_semver nvm_validate_semver \ - nvm_is_valid_semver nvm_string_contains_regexp \ + nvm_is_valid_semver nvm_string_contains_regexp nvm_trim_and_reduce_whitespace_to_one_space \ > /dev/null 2>&1 unset NVM_RC_VERSION NVM_NODEJS_ORG_MIRROR NVM_IOJS_ORG_MIRROR NVM_DIR \ NVM_CD_FLAGS NVM_BIN NVM_MAKE_JOBS \ diff --git a/test/fast/Unit tests/nvm_get_node_from_pkg_json b/test/fast/Unit tests/nvm_get_node_from_pkg_json index fee0f9f225..8d57d0743d 100755 --- a/test/fast/Unit tests/nvm_get_node_from_pkg_json +++ b/test/fast/Unit tests/nvm_get_node_from_pkg_json @@ -3,144 +3,31 @@ die () { printf "$@" ; exit 1; } \. ../../../nvm.sh - -BASIC_VERSIONS='1.2.3 -10.21.32 -x.x.x -X.X.X -*.*.* -1.2 -10.21 -x.x -X.X -*.* -1 -10 -x -* -X' - -SEMVER_OPERATORS='v -= -< -> -<= ->= -~ -^' - - - -# Dynamically generate list of semver expressions to test inside each package.json template. -# Each semver added to this list is paired with the expected output when executed with nvm_get_node_from_pkg_json. -# Most valid semvers will expecte the output to be the same as the input but there are some valid semvers with certain -# sequences of tabs/spaces whose outputs are slightly different from their inputs (documented below). - -# Format for test cases is input and expected output separated by a colon -# INPUT:EXPECTED_OUTPUT -TEST_SEMVERS='' - -BASIC_VERSIONS_COPY=$(echo "$BASIC_VERSIONS") -while [ -n "$BASIC_VERSIONS_COPY" ]; do - BASIC_VERSION=$(echo "$BASIC_VERSIONS_COPY" | head -n1) - BASIC_VERSIONS_COPY=$(echo "$BASIC_VERSIONS_COPY" | tail -n +2) - - # add test semver with just the version and no comparator - TEST_SEMVERS="$TEST_SEMVERS -$BASIC_VERSION:$BASIC_VERSION" - - SEMVER_OPERATORS_COPY=$(echo "$SEMVER_OPERATORS") - while [ -n "$SEMVER_OPERATORS_COPY" ]; do - SEMVER_OPERATOR=$(echo "$SEMVER_OPERATORS_COPY" | head -n1) - SEMVER_OPERATORS_COPY=$(echo "$SEMVER_OPERATORS_COPY" | tail -n +2) - - # add test semver with version adjacent to operator - # add test semver with version separated from operator by one space - TEST_SEMVERS="$TEST_SEMVERS -$SEMVER_OPERATOR$BASIC_VERSION:$SEMVER_OPERATOR$BASIC_VERSION -$SEMVER_OPERATOR $BASIC_VERSION:$SEMVER_OPERATOR $BASIC_VERSION" - done -done - -# add valid basic test semvers with hyphen ranges -TEST_SEMVERS="$TEST_SEMVERS -1.2.3 - 1.2.4:1.2.3 - 1.2.4 -10.21.32 - 10.21.33:10.21.32 - 10.21.33 -1.2 - 1.3:1.2 - 1.3 -10.21 - 10.22:10.21 - 10.22 -1 - 2:1 - 2 -10 - 11:10 - 11" - -# add more complex test semvers with just one comparator set -TEST_SEMVERS="$TEST_SEMVERS -1.2.3 1.2.4:1.2.3 1.2.4 -1.2 1.3:1.2 1.3 -1 2:1 2 ->1.2.3 <=1.3.0:>1.2.3 <=1.3.0" - -# add test semvers with multiple comparator sets -TEST_SEMVERS="$TEST_SEMVERS -1.2.3 || 1.2.4:1.2.3 || 1.2.4 -1.2 || 1.3:1.2 || 1.3 -1 || 2:1 || 2" - -# add test semvers that will be successfully extracted from package.json -# but will be marked invalid upon going through nvm_validate_semver -TEST_SEMVERS="$TEST_SEMVERS -1.2.3||1.2.4:1.2.3||1.2.4 -1.2||1.3:1.2||1.3 -1||2:1||2 -<1.2.3>:<1.2.3> -<1.2>:<1.2> -<1>:<1> ->>1:>>1 -<<1:<<1 -==1:==1 -**:** -xx:xx -^^1:^^1 -~~1:~~1 -1.2.3-1.2.4:1.2.3-1.2.4 -10.21.32-10.21.33:10.21.32-10.21.33 -1.2-1.3:1.2-1.3 -10.21-10.22:10.21-10.22 -1-2:1-2 -10-11:10-11" - -# add test semvers with tabs inside them -# These semvers are intended to: -# - validate that semvers can include tabs -# - validate that all tabs or consecutive tabs are reduced to one space -# - validate that leading trailing spaces and tabs are removed -TEST_SEMVERS="$TEST_SEMVERS - 1 1 :1 1 - 2 2 :2 2" +\. sharedTestResources/semvers # POSITIVE TEST CASES # (TEST SET #1) uses valid TEST_SEMVER's and valid package.json templates -TEST_SEMVERS_COPY=$(echo "$TEST_SEMVERS") -PREV_TEST_SEMVER='' -for TEMPLATE_NAME in package_json_templates/_valid_*; do - while [ -n "$TEST_SEMVERS_COPY" ]; do - LINE=$(echo "$TEST_SEMVERS_COPY" | head -n1) - TEST_SEMVERS_COPY=$(echo "$TEST_SEMVERS_COPY" | tail -n +2) - [ -n "$LINE" ] || continue - TEST_SEMVER_INPUT=$(echo "$LINE" | awk -F: '{ print $1 }') - EXPECTED_OUTPUT=$(echo "$LINE" | awk -F: '{ print $2 }') - - if [ "$PREV_TEST_SEMVER" = "$TEST_SEMVER_INPUT" ]; then - die "Problem iterating through TEST_SEMVERS_COPY (TEST SET #1). Encountered the same value twice in a row. PREV_TEST_SEMVER='$PREV_TEST_SEMVER' TEST_SEMVER_INPUT='$TEST_SEMVER_INPUT'.\n" +test_semvers_copy="$VALID_SEMVERS_FOR_PKG_JSON" +prev_semver='' +for template_file_name in package_json_templates/_valid_*; do + while [ -n "$test_semvers_copy" ]; do + semver=$(echo "$test_semvers_copy" | head -n1) + test_semvers_copy=$(echo "$test_semvers_copy" | tail -n +2) + [ -n "$semver" ] || continue + expectedOutput=$(nvm_trim_and_reduce_whitespace_to_one_space "$semver") + + if [ "$prev_semver" = "$semver" ]; then + die "Problem iterating through test_semvers_copy (TEST SET #1). Encountered the same value twice in a row. prev_semver='$prev_semver' semver='$semver'.\n" fi - PREV_TEST_SEMVER=$(printf "%s" "$TEST_SEMVER_INPUT") - - PKG_JSON_CONTENTS=$(sed 's/NODE_SEMVER/'"$TEST_SEMVER_INPUT"'/g' "$TEMPLATE_NAME" | tail -n +3) - ACTUAL_OUTPUT=$(nvm_get_node_from_pkg_json "$PKG_JSON_CONTENTS") + prev_semver="$semver" - if [ "$ACTUAL_OUTPUT" != "$EXPECTED_OUTPUT" ] || [ -z "$ACTUAL_OUTPUT" ] || [ -z "$PKG_JSON_CONTENTS" ]; then - die "'nvm_get_node_from_pkg_json' POSITIVE test case failed (TEST SET #1). Expected '$EXPECTED_OUTPUT' but got '$ACTUAL_OUTPUT' when given input template '$TEMPLATE_NAME':\n$PKG_JSON_CONTENTS" + pkg_json_contents=$(sed 's/NODE_SEMVER/'"$semver"'/g' "$template_file_name" | tail -n +3) + actual_output=$(nvm_get_node_from_pkg_json "$pkg_json_contents") + if [ "$actual_output" != "$expectedOutput" ] || [ -z "$actual_output" ] || [ -z "$pkg_json_contents" ]; then + die "'nvm_get_node_from_pkg_json' POSITIVE test case failed (TEST SET #1). Expected '$expectedOutput' but got '$actual_output' when given input '$semver' and template '$template_file_name':\n$pkg_json_contents" fi done done @@ -150,90 +37,69 @@ done # (TEST SET #2) uses valid TEST_SEMVER's but invalid package.json templates -TEST_SEMVERS_COPY=$(echo "$TEST_SEMVERS") -PREV_TEST_SEMVER='' -for TEMPLATE_NAME in package_json_templates/_invalid_*; do - while [ -n "$TEST_SEMVERS_COPY" ]; do - LINE=$(echo "$TEST_SEMVERS_COPY" | head -n1) - TEST_SEMVERS_COPY=$(echo "$TEST_SEMVERS_COPY" | tail -n +2) - [ -n "$LINE" ] || continue - TEST_SEMVER_INPUT=$(echo "$LINE" | awk -F: '{ print $1 }') - - if [ "$PREV_TEST_SEMVER" = "$TEST_SEMVER_INPUT" ]; then - die "Problem iterating through TEST_SEMVERS_COPY (TEST SET #2). Encountered the same value twice in a row. PREV_TEST_SEMVER='$PREV_TEST_SEMVER' TEST_SEMVER_INPUT='$TEST_SEMVER_INPUT'.\n" +test_semvers_copy="$VALID_SEMVERS_FOR_PKG_JSON" +prev_semver='' +for template_file_name in package_json_templates/_invalid_*; do + while [ -n "$test_semvers_copy" ]; do + semver=$(echo "$test_semvers_copy" | head -n1) + test_semvers_copy=$(echo "$test_semvers_copy" | tail -n +2) + [ -n "$semver" ] || continue + + if [ "$prev_semver" = "$semver" ]; then + die "Problem iterating through test_semvers_copy (TEST SET #2). Encountered the same value twice in a row. prev_semver='$prev_semver' semver='$semver'.\n" fi - PREV_TEST_SEMVER=$(printf "%s" "$TEST_SEMVER_INPUT") + prev_semver="$semver" - PKG_JSON_CONTENTS=$(sed 's/NODE_SEMVER/'"$TEST_SEMVER_INPUT"'/g' "$TEMPLATE_NAME" | tail -n +3) - ACTUAL_OUTPUT=$(nvm_get_node_from_pkg_json "$PKG_JSON_CONTENTS") - - if [ "$ACTUAL_OUTPUT" != "" ] || [ -z "$TEST_SEMVER_INPUT" ] || [ -z "$PKG_JSON_CONTENTS" ]; then - die "'nvm_get_node_from_pkg_json' NEGATIVE test case failed (TEST SET #2). Expected to get empty string but got '$ACTUAL_OUTPUT' when given input template '$TEMPLATE_NAME':\n$PKG_JSON_CONTENTS" + pkg_json_contents=$(sed 's/NODE_SEMVER/'"$semver"'/g' "$template_file_name" | tail -n +3) + actual_output=$(nvm_get_node_from_pkg_json "$pkg_json_contents") + if [ "$actual_output" != "" ] || [ -z "$semver" ] || [ -z "$pkg_json_contents" ]; then + die "'nvm_get_node_from_pkg_json' NEGATIVE test case failed (TEST SET #2). Expected to get empty string but got '$actual_output' when given input template '$template_file_name':\n$pkg_json_contents" fi done done -# invalid test semvers -TEST_SEMVERS='&1 -@1 -#1 -$1 -%s -1) -1( -1_ -1+ -1] -1[ -1" -1: -1? -1` -1!' - # (TEST SET #3) uses invalid TEST_SEMVER's but valid package.json templates -TEST_SEMVERS_COPY=$(echo "$TEST_SEMVERS") -PREV_TEST_SEMVER='' -for TEMPLATE_NAME in package_json_templates/_valid_*; do - while [ -n "$TEST_SEMVERS_COPY" ]; do - TEST_SEMVER_INPUT=$(echo "$TEST_SEMVERS_COPY" | head -n1) - [ -n "$TEST_SEMVER_INPUT" ] || continue - TEST_SEMVERS_COPY=$(echo "$TEST_SEMVERS_COPY" | tail -n +2) - - if [ "$PREV_TEST_SEMVER" = "$TEST_SEMVER_INPUT" ]; then - die "Problem iterating through TEST_SEMVERS_COPY (TEST SET #3). Encountered the same value twice in a row. PREV_TEST_SEMVER='$PREV_TEST_SEMVER' TEST_SEMVER_INPUT='$TEST_SEMVER_INPUT'.\n" +test_semvers_copy="$INVALID_SEMVERS_FOR_PKG_JSON" +prev_semver='' +for template_file_name in package_json_templates/_valid_*; do + while [ -n "$test_semvers_copy" ]; do + semver=$(echo "$test_semvers_copy" | head -n1) + [ -n "$semver" ] || continue + test_semvers_copy=$(echo "$test_semvers_copy" | tail -n +2) + + if [ "$prev_semver" = "$semver" ]; then + die "Problem iterating through test_semvers_copy (TEST SET #3). Encountered the same value twice in a row. prev_semver='$prev_semver' semver='$semver'.\n" fi - PREV_TEST_SEMVER=$(printf "%s" "$TEST_SEMVER_INPUT") - - PKG_JSON_CONTENTS=$(sed 's/NODE_SEMVER/'"$TEST_SEMVER_INPUT"'/g' "$TEMPLATE_NAME" | tail -n +3) - ACTUAL_OUTPUT=$(nvm_get_node_from_pkg_json "$PKG_JSON_CONTENTS") + prev_semver=$(printf "%s" "$semver") - if [ "$ACTUAL_OUTPUT" != "" ] || [ -z "$TEST_SEMVER_INPUT" ] || [ -z "$PKG_JSON_CONTENTS" ]; then - die "'nvm_get_node_from_pkg_json' NEGATIVE test case failed (TEST SET #3). Expected to get empty string but got '$ACTUAL_OUTPUT' when given input template '$TEMPLATE_NAME':\n$PKG_JSON_CONTENTS" + pkg_json_contents=$(sed 's/NODE_SEMVER/'"$semver"'/g' "$template_file_name" | tail -n +3) + actual_output=$(nvm_get_node_from_pkg_json "$pkg_json_contents") + if [ "$actual_output" != "" ] || [ -z "$semver" ] || [ -z "$pkg_json_contents" ]; then + die "'nvm_get_node_from_pkg_json' NEGATIVE test case failed (TEST SET #3). Expected to get empty string but got '$actual_output' when given input template '$template_file_name':\n$pkg_json_contents" fi done done # (TEST SET #4) uses invalid TEST_SEMVER's and invalid package.json templates -TEST_SEMVERS_COPY=$(echo "$TEST_SEMVERS") -PREV_TEST_SEMVER='' -for TEMPLATE_NAME in package_json_templates/_invalid_*; do - while [ -n "$TEST_SEMVERS_COPY" ]; do - TEST_SEMVER_INPUT=$(echo "$TEST_SEMVERS_COPY" | head -n1) - [ -n "$TEST_SEMVER_INPUT" ] || continue - TEST_SEMVERS_COPY=$(echo "$TEST_SEMVERS_COPY" | tail -n +2) - - if [ "$PREV_TEST_SEMVER" = "$TEST_SEMVER_INPUT" ]; then - die "Problem iterating through TEST_SEMVERS_COPY (TEST SET #4). Encountered the same value twice in a row. PREV_TEST_SEMVER='$PREV_TEST_SEMVER' TEST_SEMVER_INPUT='$TEST_SEMVER_INPUT'.\n" +test_semvers_copy="$INVALID_SEMVERS_FOR_PKG_JSON" +prev_semver='' +for template_file_name in package_json_templates/_invalid_*; do + while [ -n "$test_semvers_copy" ]; do + semver=$(echo "$test_semvers_copy" | head -n1) + [ -n "$semver" ] || continue + test_semvers_copy=$(echo "$test_semvers_copy" | tail -n +2) + + if [ "$prev_semver" = "$semver" ]; then + die "Problem iterating through test_semvers_copy (TEST SET #4). Encountered the same value twice in a row. prev_semver='$prev_semver' semver='$semver'.\n" fi - PREV_TEST_SEMVER=$(printf "%s" "$TEST_SEMVER_INPUT") + prev_semver=$(printf "%s" "$semver") - PKG_JSON_CONTENTS=$(sed 's/NODE_SEMVER/'"$TEST_SEMVER_INPUT"'/g' "$TEMPLATE_NAME" | tail -n +3) - ACTUAL_OUTPUT=$(nvm_get_node_from_pkg_json "$PKG_JSON_CONTENTS") - - if [ "$ACTUAL_OUTPUT" != "" ] || [ -z "$TEST_SEMVER_INPUT" ] || [ -z "$PKG_JSON_CONTENTS" ]; then - die "'nvm_get_node_from_pkg_json' NEGATIVE test case failed (TEST SET #4). Expected to get empty string but got '$ACTUAL_OUTPUT' when given input template '$TEMPLATE_NAME':\n$PKG_JSON_CONTENTS" + pkg_json_contents=$(sed 's/NODE_SEMVER/'"$semver"'/g' "$template_file_name" | tail -n +3) + actual_output=$(nvm_get_node_from_pkg_json "$pkg_json_contents") + if [ "$actual_output" != "" ] || [ -z "$semver" ] || [ -z "$pkg_json_contents" ]; then + die "'nvm_get_node_from_pkg_json' NEGATIVE test case failed (TEST SET #4). Expected to get empty string but got '$actual_output' when given input template '$template_file_name':\n$pkg_json_contents" fi done done + diff --git a/test/fast/Unit tests/nvm_interpret_node_semver b/test/fast/Unit tests/nvm_interpret_node_semver new file mode 100755 index 0000000000..28a00b14d1 --- /dev/null +++ b/test/fast/Unit tests/nvm_interpret_node_semver @@ -0,0 +1,67 @@ +#!/bin/sh + +# TODO this test currently takes about 16 minutes so it should not be in the "fast" test directory and maybe is too long regardless. + +die () { printf "$@" ; exit 1; } + +\. ../../../nvm.sh +\. sharedTestResources/semvers + +# Verify that all generated valid normalized semvers produce some result +test_cases="$VALID_SEMVERS" +while [ -n "$test_cases" ]; do + semver=$(echo "$test_cases" | head -n1) + test_cases=$(echo "$test_cases" | tail -n +2) + + output=$(nvm_interpret_node_semver "$semver") + if [ -z "$semver" ] || [ -z "$output" ] || ! nvm_string_contains_regexp "$output" '^[0-9]+\.[0-9]+\.[0-9]+$'; then + die "nvm_interpret_node_semver generated positive test case failed expecting a version to be outputted: semver: '$semver' output: '$output'" + fi +done + +# Verify that all generated invalid normalized semvers do not produce a result +test_cases="$INVALID_SEMVERS" +while [ -n "$test_cases" ]; do + semver=$(echo "$test_cases" | head -n1) + test_cases=$(echo "$test_cases" | tail -n +2) + + output=$(nvm_interpret_node_semver "$semver") + if [ -z "$semver" ] || [ -n "$output" ]; then + die "nvm_interpret_node_semver generated negative test case failed: semver: '$semver' output: '$output'" + fi +done + +# TODO add more test cases here +# Verify actual outputs given some inputs +# input:expected_output +test_cases="*:$NEWEST_NODE_VERSION +5:$NEWEST_NODE_VERSION_5 +x:$NEWEST_NODE_VERSION +X:$NEWEST_NODE_VERSION +0.12.18:0.12.18 +0.11.16:0.11.16 +222.22.2: +>0.12.18:$NEWEST_NODE_VERSION +>=0.11.16:$NEWEST_NODE_VERSION +7.1.0 || 7.3.0 || 7.2.0:7.3.0 +7.1.0 7.3.0 7.2.0: +5:$NEWEST_NODE_VERSION_5 +5.x:$NEWEST_NODE_VERSION_5 +5.x.x:$NEWEST_NODE_VERSION_5 +5.X:$NEWEST_NODE_VERSION_5 +5.X.X:$NEWEST_NODE_VERSION_5 +7.5.0:7.5.0" + +while [ -n "$test_cases" ]; do + line=$(echo "$test_cases" | head -n1) + input=$(echo "$line" | awk -F: '{ print $1 }') + expected_output=$(echo "$line" | awk -F: '{ print $2 }') + test_cases=$(echo "$test_cases" | tail -n +2) + + actualOutput=$(nvm_interpret_node_semver "$input") + if [ "$actualOutput" != "$expected_output" ] || [ -z "$input" ]; then + die "nvm_interpret_node_semver input/output test case failed. Expected output: '$expected_output'. Actual output: '$actualOutput'. Input: '$input'.\n" + fi +done +exit 0 + diff --git a/test/fast/Unit tests/nvm_interpret_semver b/test/fast/Unit tests/nvm_interpret_semver deleted file mode 100755 index b853b397e9..0000000000 --- a/test/fast/Unit tests/nvm_interpret_semver +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/sh - -# TODO this test currently takes about 1 minute and will take even longer with the rest of the needed test cases so maybe it shouldn't live in the "fast" tests directory. - -die () { printf "$@" ; exit 1; } - -\. ../../../nvm.sh - -newest_node=$(nvm_remote_version | grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+') -x5='5.12.0' - -# POSITIVE TEST CASES - -# INPUT:EXPECTED_OUTPUT -test_cases="*:$newest_node -5:$x5 -x:$newest_node -X:$newest_node -7.1.0 || 7.3.0 || 7.2.0:7.3.0 -7.1.0 7.3.0 7.2.0: -5:$x5 -5.x:$x5 -5.x.x:$x5 -5.X:$x5 -5.X.X:$x5 -7.5.0:7.5.0" - -while [ -n "$test_cases" ]; do - LINE=$(echo "$test_cases" | head -n1) - INPUT=$(echo "$LINE" | awk -F: '{ print $1 }') - EXPECTED_OUTPUT=$(echo "$LINE" | awk -F: '{ print $2 }') - ACTUAL_OUTPUT=$(nvm_interpret_node_semver "$INPUT") - if [ "$ACTUAL_OUTPUT" != "$EXPECTED_OUTPUT" ] || [ -z "$INPUT" ]; then - die "Expected output: '$EXPECTED_OUTPUT'. Actual output: '$ACTUAL_OUTPUT'. Input: '$INPUT'.\n" - fi - test_cases=$(echo "$test_cases" | tail -n +2) -done -exit 0 diff --git a/test/fast/Unit tests/nvm_is_valid_semver b/test/fast/Unit tests/nvm_is_valid_semver index 8239251e4a..88f841c253 100755 --- a/test/fast/Unit tests/nvm_is_valid_semver +++ b/test/fast/Unit tests/nvm_is_valid_semver @@ -3,39 +3,52 @@ die () { printf "$@" ; exit 1; } \. ../../../nvm.sh +\. sharedTestResources/semvers -valid_semver_regexp='^( ?(<|<=|>|>=|=|~|\^)?([0-9]+|x)\.([0-9]+|x)\.([0-9]+|x))+$' +# nvm_is_valid_semver validates that given semvers adhere to the following grammer +# +# semver ::= comparator_set ( ' || ' comparator_set )* +# comparator_set ::= comparator ( ' ' comparator )* +# comparator ::= ( '<' | '<=' | '>' | '>=' | '' ) [0-9]+ '.' [0-9]+ '.' [0-9]+ # POSITIVE TEST CASES -# STRING:REGEXP -test_cases="1.2.3:$valid_semver_regexp -11.22.33:$valid_semver_regexp" +positive_test_cases="$VALID_NORMALIZED_SEMVERS" +if [ -z "$positive_test_cases" ]; then + die "positive test cases are empty" +fi -while [ -n "$test_cases" ]; do - LINE=$(echo "$test_cases" | head -n1) - STRING=$(echo "$LINE" | awk -F: '{ print $1 }') - REGEXP=$(echo "$LINE" | awk -F: '{ print $2 }') - if [ -z "$REGEXP" ] || [ -z "$STRING" ] || ! nvm_string_contains_regexp "$STRING" "$REGEXP"; then - die "nvm_string_contains_regexp POSITIVE test case failed. REGEXP: '$REGEXP'. STRING: '$STRING'.\n" +prev_semver='' +while [ -n "$positive_test_cases" ]; do + semver=$(echo "$positive_test_cases" | head -n1) + if [ -z "$semver" ] || ! nvm_is_valid_semver "$semver"; then + die "nvm_string_contains_regexp POSITIVE test case failed. semver: '$semver'.\n" fi - test_cases=$(echo "$test_cases" | tail -n +2) + if [ "$prev_semver" = "$semver" ]; then + die "something is wrong. positive test cases received the same test case twice in a row. semver: '$semver'" + fi + prev_semver="$semver" + positive_test_cases=$(echo "$positive_test_cases" | tail -n +2) done # NEGATIVE TEST CASES -# STRING:REGEXP -test_cases="1.2.a:$valid_semver_regexp -:$valid_semver_regexp -11.22.a:$valid_semver_regexp" - -while [ -n "$test_cases" ]; do - LINE=$(echo "$test_cases" | head -n1) - STRING=$(echo "$LINE" | awk -F: '{ print $1 }') - REGEXP=$(echo "$LINE" | awk -F: '{ print $2 }') - if [ -z "$REGEXP" ] || nvm_string_contains_regexp "$STRING" "$REGEXP"; then - die "nvm_string_contains_regexp NEGATIVE test case failed. REGEXP: '$REGEXP'. STRING: '$STRING'.\n" +negative_test_cases="$VALID_NON_NORMALIZED_SEMVERS" +if [ -z "$negative_test_cases" ]; then + die "negative test cases are empty" +fi + +prev_semver='initialized to non empty string' +while [ -n "$negative_test_cases" ]; do + semver=$(echo "$negative_test_cases" | head -n1) + if nvm_is_valid_semver "$semver"; then + die "nvm_string_contains_regexp NEGATIVE test case failed. semver: '$semver'.\n" fi - test_cases=$(echo "$test_cases" | tail -n +2) + if [ "$prev_semver" = "$semver" ]; then + die "something is wrong. negative test cases received the same test case twice in a row. semver: '$semver'" + fi + prev_semver="$semver" + negative_test_cases=$(echo "$negative_test_cases" | tail -n +2) done exit 0 + diff --git a/test/fast/Unit tests/nvm_string_equals_regexp b/test/fast/Unit tests/nvm_string_equals_regexp index 8239251e4a..a43f30af1d 100755 --- a/test/fast/Unit tests/nvm_string_equals_regexp +++ b/test/fast/Unit tests/nvm_string_equals_regexp @@ -8,34 +8,36 @@ valid_semver_regexp='^( ?(<|<=|>|>=|=|~|\^)?([0-9]+|x)\.([0-9]+|x)\.([0-9]+|x))+ # POSITIVE TEST CASES -# STRING:REGEXP +# TODO add more test cases +# string:regexp test_cases="1.2.3:$valid_semver_regexp 11.22.33:$valid_semver_regexp" while [ -n "$test_cases" ]; do - LINE=$(echo "$test_cases" | head -n1) - STRING=$(echo "$LINE" | awk -F: '{ print $1 }') - REGEXP=$(echo "$LINE" | awk -F: '{ print $2 }') - if [ -z "$REGEXP" ] || [ -z "$STRING" ] || ! nvm_string_contains_regexp "$STRING" "$REGEXP"; then - die "nvm_string_contains_regexp POSITIVE test case failed. REGEXP: '$REGEXP'. STRING: '$STRING'.\n" + line=$(echo "$test_cases" | head -n1) + string=$(echo "$line" | awk -F: '{ print $1 }') + regexp=$(echo "$line" | awk -F: '{ print $2 }') + if [ -z "$regexp" ] || [ -z "$string" ] || ! nvm_string_contains_regexp "$string" "$regexp"; then + die "nvm_string_contains_regexp POSITIVE test case failed. regexp: '$regexp'. string: '$string'.\n" fi test_cases=$(echo "$test_cases" | tail -n +2) done # NEGATIVE TEST CASES -# STRING:REGEXP +# string:regexp test_cases="1.2.a:$valid_semver_regexp :$valid_semver_regexp 11.22.a:$valid_semver_regexp" while [ -n "$test_cases" ]; do - LINE=$(echo "$test_cases" | head -n1) - STRING=$(echo "$LINE" | awk -F: '{ print $1 }') - REGEXP=$(echo "$LINE" | awk -F: '{ print $2 }') - if [ -z "$REGEXP" ] || nvm_string_contains_regexp "$STRING" "$REGEXP"; then - die "nvm_string_contains_regexp NEGATIVE test case failed. REGEXP: '$REGEXP'. STRING: '$STRING'.\n" + line=$(echo "$test_cases" | head -n1) + string=$(echo "$line" | awk -F: '{ print $1 }') + regexp=$(echo "$line" | awk -F: '{ print $2 }') + if [ -z "$regexp" ] || nvm_string_contains_regexp "$string" "$regexp"; then + die "nvm_string_contains_regexp NEGATIVE test case failed. regexp: '$regexp'. string: '$string'.\n" fi test_cases=$(echo "$test_cases" | tail -n +2) done exit 0 + diff --git a/test/fast/Unit tests/nvm_trim_and_reduce_whitespace_to_one_space b/test/fast/Unit tests/nvm_trim_and_reduce_whitespace_to_one_space new file mode 100755 index 0000000000..52f5f0ae45 --- /dev/null +++ b/test/fast/Unit tests/nvm_trim_and_reduce_whitespace_to_one_space @@ -0,0 +1,28 @@ +#!/bin/sh + +die () { printf "$@" ; exit 1; } + +\. ../../../nvm.sh + +# TODO add more test cases +# input:expected_output +test_cases="1.2.3:$valid_semver_regexp +11.22.33:$valid_semver_regexp" + +test_cases='1:1 + 1.2.3:1.2.3 + 1 1 :1 1 + 2 2 :2 2' + +while [ -n "$test_cases" ]; do + line=$(echo "$test_cases" | head -n1) + input=$(echo "$line" | awk -F: '{ print $1 }') + expected_output=$(echo "$line" | awk -F: '{ print $2 }') + actual_output=$(nvm_trim_and_reduce_whitespace_to_one_space "$input") + if [ -z "$input" ] || [ -z "$expected_output" ] || [ "$expected_output" != "$actual_output" ]; then + die "nvm_reduce_whitespace_to_one_space test case failed. expected_output: '$expected_output'. actual_output: '$actual_output'.\n" + fi + test_cases=$(echo "$test_cases" | tail -n +2) +done +exit 0 + diff --git a/test/fast/Unit tests/nvm_validate_semver b/test/fast/Unit tests/nvm_validate_semver index a5cc64b4eb..44b0374141 100755 --- a/test/fast/Unit tests/nvm_validate_semver +++ b/test/fast/Unit tests/nvm_validate_semver @@ -4,7 +4,9 @@ die () { printf "$@" ; exit 1; } \. ../../../nvm.sh -# INPUT:EXPECTED_OUTPUT +# TODO add more test cases here +# Some test cases should just test that valid semvers from sharedTestResources/semvers just produce some result +# input:expected_output test_cases="1.2.3:1.2.3 1.2.3 - 1.2.4:>=1.2.3 <=1.2.4 1.2.3-1.2.4:>=1.2.3 <=1.2.4 @@ -41,13 +43,14 @@ a || 1.2.3: 11.22.33:11.22.33" while [ -n "$test_cases" ]; do - LINE=$(echo "$test_cases" | head -n1) - INPUT=$(echo "$LINE" | awk -F: '{ print $1 }') - EXPECTED_OUTPUT=$(echo "$LINE" | awk -F: '{ print $2 }') - ACTUAL_OUTPUT=$(nvm_validate_semver "$INPUT") - if [ -z "$INPUT" ] || [ "$ACTUAL_OUTPUT" != "$EXPECTED_OUTPUT" ]; then - die "nvm_validate_semver test case failed. Expected output: '$EXPECTED_OUTPUT'. Actual output: '$ACTUAL_OUTPUT'. Input: '$INPUT'.\n" + line=$(echo "$test_cases" | head -n1) + input=$(echo "$line" | awk -F: '{ print $1 }') + expected_output=$(echo "$line" | awk -F: '{ print $2 }') + actual_output=$(nvm_validate_semver "$input") + if [ -z "$input" ] || [ "$actual_output" != "$expected_output" ]; then + die "nvm_validate_semver test case failed. Expected output: '$expected_output'. Actual output: '$actual_output'. Input: '$input'.\n" fi test_cases=$(echo "$test_cases" | tail -n +2) done exit 0 + diff --git a/test/fast/Unit tests/sharedTestResources/semvers b/test/fast/Unit tests/sharedTestResources/semvers new file mode 100755 index 0000000000..9f1f8916a6 --- /dev/null +++ b/test/fast/Unit tests/sharedTestResources/semvers @@ -0,0 +1,187 @@ +#!/bin/sh + +NEWEST_NODE_VERSION=$(nvm_remote_version | grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+') +NEWEST_NODE_VERSION_5='5.12.0' + +# The only operators prefixing versions that would be acceptable inputs to the semver interpretation logic. +VALID_NORMALIZED_SEMVER_OPERATORS='< +> +<= +>=' + +# Valid semver operators that would be acceptable to find prefixing a semver in a package.json file, but would need to be validated/normalized before interpretting. +VALID_NON_NORMALIZED_SEMVER_OPERATORS='v += +~ +^' + +# Versions (stripped of any operators) that are considered valid inputs to the semver interpretation logic. +VALID_NORMALIZED_VERSIONS='4.1.0 +0.12.18 +0.11.16 +6.11.4 +10.0.0' + +# Semvers that won't be pulled from package.json files because they contain characters that are not included in valid semvers +INVALID_SEMVERS_FOR_PKG_JSON='&1 +# +$ +@ +! +% +& +) +( ++ +@1 +#1 +$1 +%s +1) +1( +1_ +1+ +1] +1[ +1" +1: +1? +1` +1!' + +# Semvers that won't resolve to a node version +INVALID_SEMVERS="$INVALID_SEMVERS_FOR_PKG_JSON +~1 +^1 +- += +^ +1 +a +asdf +1111 +1 1 +1. +1.1 +1.* +1.2 +11.222 +1.2.a +1.*.* +1.x.x +11.22.a +=1.2.3 +~1.2.3 +^1.2.3 +1.1.1 2.2.2 +>1.1.1 <1.1.0 +1.2 - 1.3 +10.221.32 - 10.21.33 +10.212 - 10.22 +1.2.3 - 1.2.4 +1.2.3-1.2.4 +1.2 1.3 +1 2 +1.2.3||1.2.4 +1.2||1.3 +1||2 +>1000 +<0" + +# Valid semvers that should resolve to a node version and are slightly more complex than the [operator][version] structure +VALID_NORMALIZED_COMPLEX_SEMVERS='10.3.0 || 8.1.1 || 4.1.0 +7.7.2 || >=9.0.0 <=8.9.0 || <8.2.1 +8.2.0 8.2.0 +>4.0.0 <=5.0.0 +8.0.0 || <6.12.0' + +# Valid semvers that should resolve to a node version but need to be validated/normalized before interpretting. +VALID_NON_NORMALIZED_SEMVERS='x +X +* +x.x +X.X +*.* +x.x.x +X.X.X +x.X.* +*.x.X +x.1.2 +>1.1.1 <6.2.2 +> 1.1.1 <6.2.2 +10 - 11 +10-11 +4.2.2||8.1.1 +4.2 || 1.3 +4 || 2' + +# Strings that should be extracted from a package.json engines.node value but don't need to resolve to a node version. +VALID_COMPLEX_SEMVERS_FOR_PKG_JSON="$VALID_NORMALIZED_COMPLEX_SEMVERS +<1.2.3> +<1.2> +<1> +>>1 +<<1 +==1 +** +xx +^^1 +~~1 +10.211.32-10.211.33 +10.211-10.222 + 1 1 + 2 2 " + +die () { printf "$@" ; exit 1; } + +generate_semvers() { + versions="${1-}" + operators="${2-}" + should_add_spacing_permutations=${3-1} + if [ -z "$versions" ] || [ -z "$operators" ]; then + die "Problem generating semvers: Given invalid parameters. versions: '$versions' operators: '$operators'" + fi + while [ -n "$versions" ]; do + version=$(echo "$versions" | head -n1) + versions=$(echo "$versions" | tail -n +2) + + operators_copy="$operators" + while [ -n "$operators_copy" ]; do + operator=$(echo "$operators_copy" | head -n1) + operators_copy=$(echo "$operators_copy" | tail -n +2) + if [ -z "$semvers" ]; then + # NOTE: the third spacing permutation of the operator has a tab between the operator and version. + if [ $should_add_spacing_permutations -eq 0 ]; then + semvers=$(printf "%s\n%s\n%s" "${operator}${version}" "${operator} ${version}" "${operator} ${version}") + else + semvers="${operator}${version}" + fi + else + # NOTE: the third spacing permutation of the operator has a tab between the operator and version. + if [ $should_add_spacing_permutations -eq 0 ]; then + semvers=$(printf "%s\n%s\n%s\n%s" "$semvers" "${operator}${version}" "${operator} ${version}" "${operator} ${version}") + else + semvers=$(printf "%s\n%s" "$semvers" "${operator}${version}") + fi + fi + done + done + echo "$semvers" +} + +VALID_NORMALIZED_SEMVERS=$(printf "%s\n%s\n%s" \ + "$VALID_NORMALIZED_COMPLEX_SEMVERS" \ + "$VALID_NORMALIZED_VERSIONS" \ + "$(generate_semvers "$VALID_NORMALIZED_VERSIONS" "$VALID_NORMALIZED_SEMVER_OPERATORS")" \ +) + +VALID_SEMVERS=$(printf "%s\n%s\n%s" \ + "$VALID_NORMALIZED_SEMVERS" \ + "$VALID_NON_NORMALIZED_SEMVERS" \ + "$(generate_semvers "$VALID_NORMALIZED_VERSIONS" "$VALID_NON_NORMALIZED_SEMVER_OPERATORS" 0)" \ +) + +VALID_SEMVERS_FOR_PKG_JSON=$(printf "%s\n%s" \ + "$VALID_SEMVERS" \ + "$VALID_COMPLEX_SEMVERS_FOR_PKG_JSON" \ +) From 7c888aa4e52bd0659f123979c93f843648dc2ad3 Mon Sep 17 00:00:00 2001 From: edwmurph Date: Thu, 31 May 2018 06:26:52 -0400 Subject: [PATCH 08/12] fixed eclint problem; renamed some files/functions --- .editorconfig | 7 +++- nvm.sh | 34 ++++++++----------- .../Unit tests/nvm_get_node_from_pkg_json | 4 +-- test/fast/Unit tests/nvm_is_valid_semver | 4 +-- ...m_validate_semver => nvm_normalize_semver} | 8 ++--- ...uals_regexp => nvm_string_contains_regexp} | 0 .../semvers => generated_semvers.sh} | 0 .../nvm_interpret_node_semver | 6 ++-- 8 files changed, 29 insertions(+), 34 deletions(-) rename test/fast/Unit tests/{nvm_validate_semver => nvm_normalize_semver} (86%) rename test/fast/Unit tests/{nvm_string_equals_regexp => nvm_string_contains_regexp} (100%) rename test/{fast/Unit tests/sharedTestResources/semvers => generated_semvers.sh} (100%) rename test/{fast/Unit tests => slow}/nvm_interpret_node_semver (91%) diff --git a/.editorconfig b/.editorconfig index b368348c0a..a5403e1921 100644 --- a/.editorconfig +++ b/.editorconfig @@ -18,6 +18,11 @@ indent_style = tab [test/fast/Unit tests/package_json_templates/*] indent_style = unset -[test/fast/Unit tests/nvm_get_node_from_pkg_json] +[test/generated_semvers.sh] indent_style = unset indent_size = unset + +[test/fast/Unit tests/nvm_trim_and_reduce_whitespace_to_one_space] +indent_style = unset +indent_size = unset + diff --git a/nvm.sh b/nvm.sh index fddc9393bd..fb61abe81d 100644 --- a/nvm.sh +++ b/nvm.sh @@ -318,14 +318,9 @@ nvm_string_contains_regexp() { # comparator_set ::= comparator ( ' ' comparator )* # comparator ::= ( '<' | '<=' | '>' | '>=' | '' ) [0-9]+ '.' [0-9]+ '.' [0-9]+ nvm_is_valid_semver() { - if nvm_string_contains_regexp "${1-}" '^( ?(<|<=|>|>=)?[0-9]+\.[0-9]+\.[0-9]+)+( \|\| ( ?(<|<=|>|>=)?[0-9]+\.[0-9]+\.[0-9]+)+)*$'; then - return 0 - else - return 1 - fi + nvm_string_contains_regexp "${1-}" '^( ?(<|<=|>|>=)?[0-9]+\.[0-9]+\.[0-9]+)+( \|\| ( ?(<|<=|>|>=)?[0-9]+\.[0-9]+\.[0-9]+)+)*$' } -# TODO figure out if the commented out logic is needed anywhere nvm_trim_and_reduce_whitespace_to_one_space() { command printf "%s" "${1-}" | command tr -d '\n\r' | @@ -334,14 +329,13 @@ nvm_trim_and_reduce_whitespace_to_one_space() { command sed 's/^ //; s/ $//; s/^ //' } -# TODO rename this function to 'nvm_normalize_semver' -# Attempts to convert given semver to the following grammar: +# Attempts to normalize the given semver to the following grammar: # # semver ::= comparator_set ( ' || ' comparator_set )* # comparator_set ::= comparator ( ' ' comparator )* # comparator ::= ( '<' | '<=' | '>' | '>=' | '' ) [0-9]+ '.' [0-9]+ '.' [0-9]+ -nvm_validate_semver() { - # split the semantic version into comparator_set's +nvm_normalize_semver() { + # split the semantic version's comparator_set's onto their own lines for iteration local semver semver=$(nvm_trim_and_reduce_whitespace_to_one_space "${1-}" | command tr '||' '\n') if [ -z "$semver" ]; then @@ -395,7 +389,7 @@ nvm_validate_semver() { s/ (=|v)//g; " \ | command awk '{ - # handle conversions of comparators with '^' or '~' or 'x' into required grammar + # handle conversions of comparators with ^ or ~ or x into required grammar # ` ^0.0.1 ` => ` >=0.0.1 <0.0.2 ` # ` ^0.1.2 ` => ` >=0.1.2 <0.2.0 ` # ` ^1.2.3 ` => ` >=1.2.3 <2.0.0 ` @@ -537,7 +531,7 @@ nvm_interpret_complex_semver() { [ -n "$current_comparator" ] || continue local stripped_version_from_comparator - stripped_version_from_comparator="$(command printf "%s" "$current_comparator" | nvm_grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+$')" + stripped_version_from_comparator=$(command printf "%s" "$current_comparator" | nvm_grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+$') if [ -z "$stripped_version_from_comparator" ]; then return 1 fi @@ -638,6 +632,8 @@ nvm_interpret_complex_semver() { done if [ "$highest_compatible_version" != '0.0.0' ]; then command printf "%s" "$highest_compatible_version" + else + return 1 fi } @@ -651,7 +647,7 @@ nvm_interpret_simple_semver() { return 1 fi local stripped_version_from_semver - stripped_version_from_semver="$(command printf "%s" "$semver" | nvm_grep -o '^[0-9]\+\.[0-9]\+\.[0-9]\+$')" + stripped_version_from_semver=$(command printf "%s" "$semver" | nvm_grep -o '^[0-9]\+\.[0-9]\+\.[0-9]\+$') local newest_version_from_list newest_version_from_list=$(command printf "%s" "$version_list" | tail -n 1 | nvm_grep -o '^[0-9]\+\.[0-9]\+\.[0-9]\+$') if [ -z "$stripped_version_from_semver" ] || [ -z "$newest_version_from_list" ]; then @@ -713,7 +709,7 @@ nvm_interpret_node_semver() { # Validate incoming semver and transform it into the grammar that is expected by the following logic local valid_transformed_semver - valid_transformed_semver=$(nvm_validate_semver "$semver") + valid_transformed_semver=$(nvm_normalize_semver "$semver") if [ -z "$valid_transformed_semver" ]; then return 1 fi @@ -797,13 +793,13 @@ nvm_get_node_from_pkg_json() { return 0 fi done - return 2 + return 1 } nvm_package_json_version() { export RESOLVED_PKG_JSON_VERSION='' local pkg_json_path - pkg_json_path="$(nvm_find_package_json)" + pkg_json_path=$(nvm_find_package_json) if [ ! -e "${pkg_json_path}" ]; then nvm_err "No package.json file found" return 1 @@ -812,14 +808,14 @@ nvm_package_json_version() { pkg_json_semver=$(nvm_get_node_from_pkg_json "$(command cat "$pkg_json_path")" || command printf '') if [ ! -n "${pkg_json_semver}" ]; then nvm_err "Warning: could not retrieve engines.node semver expression in package.json file found at \"${pkg_json_path}\"" - return 2 + return 1 else nvm_echo "Found '${pkg_json_path}' with semver expression <${pkg_json_semver}>" # attempt complex semver range evaluation RESOLVED_PKG_JSON_VERSION=$(nvm_interpret_node_semver "$pkg_json_semver") if [ ! -n "${RESOLVED_PKG_JSON_VERSION}" ]; then nvm_err "Warning: could not interpret engines.node semver expression obtained from package.json file." - return 2 + return 1 fi fi } @@ -4081,7 +4077,7 @@ nvm() { node_version_has_solaris_binary iojs_version_has_solaris_binary \ nvm_curl_libz_support nvm_command_info \ nvm_get_node_from_pkg_json nvm_find_package_json nvm_package_json_version \ - nvm_interpret_node_semver nvm_interpret_simple_semver nvm_interpret_complex_semver nvm_validate_semver \ + nvm_interpret_node_semver nvm_interpret_simple_semver nvm_interpret_complex_semver nvm_normalize_semver \ nvm_is_valid_semver nvm_string_contains_regexp nvm_trim_and_reduce_whitespace_to_one_space \ > /dev/null 2>&1 unset NVM_RC_VERSION NVM_NODEJS_ORG_MIRROR NVM_IOJS_ORG_MIRROR NVM_DIR \ diff --git a/test/fast/Unit tests/nvm_get_node_from_pkg_json b/test/fast/Unit tests/nvm_get_node_from_pkg_json index 8d57d0743d..163f293790 100755 --- a/test/fast/Unit tests/nvm_get_node_from_pkg_json +++ b/test/fast/Unit tests/nvm_get_node_from_pkg_json @@ -1,9 +1,7 @@ #!/bin/sh -die () { printf "$@" ; exit 1; } - \. ../../../nvm.sh -\. sharedTestResources/semvers +\. ../../generated_semvers.sh # POSITIVE TEST CASES diff --git a/test/fast/Unit tests/nvm_is_valid_semver b/test/fast/Unit tests/nvm_is_valid_semver index 88f841c253..cba9cd1bf8 100755 --- a/test/fast/Unit tests/nvm_is_valid_semver +++ b/test/fast/Unit tests/nvm_is_valid_semver @@ -1,9 +1,7 @@ #!/bin/sh -die () { printf "$@" ; exit 1; } - \. ../../../nvm.sh -\. sharedTestResources/semvers +\. ../../generated_semvers.sh # nvm_is_valid_semver validates that given semvers adhere to the following grammer # diff --git a/test/fast/Unit tests/nvm_validate_semver b/test/fast/Unit tests/nvm_normalize_semver similarity index 86% rename from test/fast/Unit tests/nvm_validate_semver rename to test/fast/Unit tests/nvm_normalize_semver index 44b0374141..3b6fd9d76f 100755 --- a/test/fast/Unit tests/nvm_validate_semver +++ b/test/fast/Unit tests/nvm_normalize_semver @@ -1,11 +1,11 @@ #!/bin/sh -die () { printf "$@" ; exit 1; } - \. ../../../nvm.sh +\. ../../generated_semvers.sh # TODO add more test cases here -# Some test cases should just test that valid semvers from sharedTestResources/semvers just produce some result +# Some test cases should just test that valid semvers from 'Shared test tesources'/semvers just produce some result + # input:expected_output test_cases="1.2.3:1.2.3 1.2.3 - 1.2.4:>=1.2.3 <=1.2.4 @@ -46,7 +46,7 @@ while [ -n "$test_cases" ]; do line=$(echo "$test_cases" | head -n1) input=$(echo "$line" | awk -F: '{ print $1 }') expected_output=$(echo "$line" | awk -F: '{ print $2 }') - actual_output=$(nvm_validate_semver "$input") + actual_output=$(nvm_normalize_semver "$input") if [ -z "$input" ] || [ "$actual_output" != "$expected_output" ]; then die "nvm_validate_semver test case failed. Expected output: '$expected_output'. Actual output: '$actual_output'. Input: '$input'.\n" fi diff --git a/test/fast/Unit tests/nvm_string_equals_regexp b/test/fast/Unit tests/nvm_string_contains_regexp similarity index 100% rename from test/fast/Unit tests/nvm_string_equals_regexp rename to test/fast/Unit tests/nvm_string_contains_regexp diff --git a/test/fast/Unit tests/sharedTestResources/semvers b/test/generated_semvers.sh similarity index 100% rename from test/fast/Unit tests/sharedTestResources/semvers rename to test/generated_semvers.sh diff --git a/test/fast/Unit tests/nvm_interpret_node_semver b/test/slow/nvm_interpret_node_semver similarity index 91% rename from test/fast/Unit tests/nvm_interpret_node_semver rename to test/slow/nvm_interpret_node_semver index 28a00b14d1..c9b484fe0a 100755 --- a/test/fast/Unit tests/nvm_interpret_node_semver +++ b/test/slow/nvm_interpret_node_semver @@ -1,11 +1,9 @@ #!/bin/sh -# TODO this test currently takes about 16 minutes so it should not be in the "fast" test directory and maybe is too long regardless. - die () { printf "$@" ; exit 1; } -\. ../../../nvm.sh -\. sharedTestResources/semvers +\. ../../nvm.sh +\. ../generated_semvers.sh # Verify that all generated valid normalized semvers produce some result test_cases="$VALID_SEMVERS" From b057b5660f70dcae703aba3546da2a13b61a7572 Mon Sep 17 00:00:00 2001 From: edwmurph Date: Thu, 31 May 2018 10:40:41 -0400 Subject: [PATCH 09/12] fix zsh incompatibility --- nvm.sh | 97 +++++++++++------------ test/fast/Unit tests/nvm_normalize_semver | 2 +- 2 files changed, 49 insertions(+), 50 deletions(-) diff --git a/nvm.sh b/nvm.sh index fb61abe81d..028a5ccf91 100644 --- a/nvm.sh +++ b/nvm.sh @@ -341,54 +341,53 @@ nvm_normalize_semver() { if [ -z "$semver" ]; then return 1 fi + local comparator_set + local validated_comparator_set local validated_semver validated_semver=''; while [ -n "$semver" ]; do - local comparator_set comparator_set=$(command printf "%s" "$semver" | command head -n1) semver=$(command printf "%s" "$semver" | command tail -n +2) [ -n "$comparator_set" ] || continue # convert comparators into required grammar - local validated_comparator_set - validated_comparator_set=$(command printf " %s " "$comparator_set" \ - | command sed -E " - # exactly 1 space is needed before and after every comparator (including the first and last comparators) - s/\011/ /g;s/ +/ /g; - - # normalize all wildcards to x - s/X|\*/x/g; - - # space out numbers surrounding '-' - s/ ?- ?/ - /g; - - # ' 1 ' => ' 1.x.x ' - # ' x ' => ' x.x.x ' - s/ ([0-9]+|x) / \1.x.x /g; - - # ' 1.2 ' => ' 1.2.x ' - # ' 1.x ' => ' 1.x.x ' - # ' x.x ' => ' x.x.x ' - s/ (([0-9]+|x)\.([0-9]+|x)) / \1.x /g; - - # ' 1.2.3 - 1.2.4 ' => ' >=1.2.3 <=1.2.4 ' - s/ (([0-9]+|x)\.([0-9]+|x)\.([0-9]+|x)) ?\- ?(([0-9]+|x)\.([0-9]+|x)\.([0-9]+|x)) / >=\1 <=\5 /g; - - # ' > 1.2.3 ' => ' >1.2.3 ' - # ' < 1.2.5 ' => ' <1.2.5 ' - # ' <= 1.2.3 ' => ' <=1.2.3 ' - # ' >= 1.2.3 ' => ' >=1.2.3 ' - # ' = 1.2.3 ' => ' =1.2.3 ' - # ' ~ 1.2.3 ' => ' ~1.2.3 ' - # ' ^ 1.2.3 ' => ' ^1.2.3 ' - # ' v 1.2.3 ' => ' v1.2.3 ' - s/ (v|<|>|<=|>=|=|~|\^) (([0-9]+|x)\.([0-9]+|x)\.([0-9]+|x)) / \1\2 /g; - - # ' =1.2.3 ' => ' 1.2.3 ' - # ' v1.2.3 ' => ' 1.2.3 ' - s/ (=|v)//g; - " \ - | command awk '{ + validated_comparator_set=$(command printf " %s " "$comparator_set" | + command sed -E " + # exactly 1 space is needed before and after every comparator (including the first and last comparators) + s/\011/ /g;s/ +/ /g; + + # normalize all wildcards to x + s/X|\*/x/g; + + # space out numbers surrounding '-' + s/ ?- ?/ - /g; + + # ' 1 ' => ' 1.x.x ' + # ' x ' => ' x.x.x ' + s/ ([0-9]+|x) / \1.x.x /g; + + # ' 1.2 ' => ' 1.2.x ' + # ' 1.x ' => ' 1.x.x ' + # ' x.x ' => ' x.x.x ' + s/ (([0-9]+|x)\.([0-9]+|x)) / \1.x /g; + + # ' 1.2.3 - 1.2.4 ' => ' >=1.2.3 <=1.2.4 ' + s/ (([0-9]+|x)\.([0-9]+|x)\.([0-9]+|x)) ?\- ?(([0-9]+|x)\.([0-9]+|x)\.([0-9]+|x)) / >=\1 <=\5 /g; + + # ' > 1.2.3 ' => ' >1.2.3 ' + # ' < 1.2.5 ' => ' <1.2.5 ' + # ' <= 1.2.3 ' => ' <=1.2.3 ' + # ' >= 1.2.3 ' => ' >=1.2.3 ' + # ' = 1.2.3 ' => ' =1.2.3 ' + # ' ~ 1.2.3 ' => ' ~1.2.3 ' + # ' ^ 1.2.3 ' => ' ^1.2.3 ' + # ' v 1.2.3 ' => ' v1.2.3 ' + s/ (v|<|>|<=|>=|=|~|\^) (([0-9]+|x)\.([0-9]+|x)\.([0-9]+|x)) / \1\2 /g; + + # ' =1.2.3 ' => ' 1.2.3 ' + # ' v1.2.3 ' => ' 1.2.3 ' + s/ (=|v)//g;" | + command awk '{ # handle conversions of comparators with ^ or ~ or x into required grammar # ` ^0.0.1 ` => ` >=0.0.1 <0.0.2 ` # ` ^0.1.2 ` => ` >=0.1.2 <0.2.0 ` @@ -462,8 +461,8 @@ nvm_normalize_semver() { } } print output - }' \ - | command sed -E 's/^ +//;s/ +$//' + }' | + command sed -E 's/^ +//;s/ +$//' ) # only comparator_sets composed of the required grammar are marked as valid @@ -500,13 +499,17 @@ nvm_interpret_complex_semver() { # - Add the discovered newest compatible version to highest_compatible_versions. # - Choose the highest version among all the versions collected in highest_compatible_versions. semver=$(command printf "%s" "$semver" | command tr '||' '\n') + local version_list_copy + local current_comparator_set + local current_version + local current_comparator_set_copy + local current_comparator + local stripped_version_from_comparator local highest_compatible_versions highest_compatible_versions='' while [ -n "$semver" ]; do - local version_list_copy version_list_copy=$(command printf "%s" "$version_list") - local current_comparator_set current_comparator_set=$(command printf "%s" "$semver" | command head -n1 | command sed -E 's/^ +//;s/ +$//') semver=$(command printf "%s" "$semver" | command tail -n +2) [ -n "$current_comparator_set" ] || continue @@ -515,22 +518,18 @@ nvm_interpret_complex_semver() { # - If current_version satisfies all comparators in current_comparator_set, we've found the newest version compatible with all comparators in current current_comparator_set. # - Add discovered version to highest_compatible_versions and stop iterating through versions for current_comparator_set. while [ -n "$version_list_copy" ]; do - local current_version current_version=$(command printf "%s" "$version_list_copy" | command tail -n1 | command sed -E 's/^ +//;s/ +$//' | nvm_grep -o '^[0-9]\+\.[0-9]\+\.[0-9]\+$') version_list_copy=$(command printf "%s" "$version_list_copy" | command sed '$d') [ -n "$current_version" ] || continue # For each comparator in the current_comparator_set_copy: # - If current_version is compatible with all comparators, we know current_version is the newest compatible version - local current_comparator_set_copy current_comparator_set_copy=$(command printf "%s" "$current_comparator_set" | command tr ' ' '\n') while [ -n "$current_comparator_set_copy" ]; do - local current_comparator current_comparator=$(command printf "%s" "$current_comparator_set_copy" | command head -n1 | command sed -E 's/^ +//;s/ +$//') current_comparator_set_copy=$(command printf "%s" "$current_comparator_set_copy" | command tail -n +2) [ -n "$current_comparator" ] || continue - local stripped_version_from_comparator stripped_version_from_comparator=$(command printf "%s" "$current_comparator" | nvm_grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+$') if [ -z "$stripped_version_from_comparator" ]; then return 1 @@ -618,10 +617,10 @@ nvm_interpret_complex_semver() { # Since comparator sets are separated by '||', choosing any of the highest versions compatible with any of the comparator_sets would be compatible with the whole semver. # Therefore, we should resolve to the highest version in highest_compatible_versions. local highest_compatible_version + local compatible_node_version highest_compatible_version='0.0.0' highest_compatible_versions=$(command printf "%s" "$highest_compatible_versions" | command tr ' ' '\n') while [ -n "$highest_compatible_versions" ]; do - local compatible_node_version compatible_node_version=$(command printf "%s" "$highest_compatible_versions" | command head -n1 | command sed -E 's/^ +//;s/ +$//') highest_compatible_versions=$(command printf "%s" "$highest_compatible_versions" | command tail -n +2) [ -n "$compatible_node_version" ] || continue diff --git a/test/fast/Unit tests/nvm_normalize_semver b/test/fast/Unit tests/nvm_normalize_semver index 3b6fd9d76f..8c7223b22a 100755 --- a/test/fast/Unit tests/nvm_normalize_semver +++ b/test/fast/Unit tests/nvm_normalize_semver @@ -48,7 +48,7 @@ while [ -n "$test_cases" ]; do expected_output=$(echo "$line" | awk -F: '{ print $2 }') actual_output=$(nvm_normalize_semver "$input") if [ -z "$input" ] || [ "$actual_output" != "$expected_output" ]; then - die "nvm_validate_semver test case failed. Expected output: '$expected_output'. Actual output: '$actual_output'. Input: '$input'.\n" + die "nvm_normalize_semver test case failed. Expected output: '$expected_output'. Actual output: '$actual_output'. Input: '$input'.\n" fi test_cases=$(echo "$test_cases" | tail -n +2) done From faadbf8f6c02266d27e55c883263584f16de791f Mon Sep 17 00:00:00 2001 From: edwmurph Date: Thu, 31 May 2018 11:15:49 -0400 Subject: [PATCH 10/12] added a couple minor optimizations; polished a few things --- nvm.sh | 81 +++++++++++++++++++++++++++++----------------------------- 1 file changed, 41 insertions(+), 40 deletions(-) diff --git a/nvm.sh b/nvm.sh index 028a5ccf91..d632f56a42 100644 --- a/nvm.sh +++ b/nvm.sh @@ -309,7 +309,7 @@ nvm_string_contains_regexp() { return 1 fi # e.g. "nvm_string_contains_regexp abbc ^aa?b+.$" returns 0 - command printf "%s" "$string" | command awk "/$regexp/{ exit 0 }{ exit 1 }" + command printf '%s' "$string" | command awk "/$regexp/{ exit 0 }{ exit 1 }" } # Validates that the given semver adheres to the following grammar: @@ -322,7 +322,7 @@ nvm_is_valid_semver() { } nvm_trim_and_reduce_whitespace_to_one_space() { - command printf "%s" "${1-}" | + command printf '%s' "${1-}" | command tr -d '\n\r' | command tr '\t' ' ' | command tr -s ' ' | @@ -346,12 +346,12 @@ nvm_normalize_semver() { local validated_semver validated_semver=''; while [ -n "$semver" ]; do - comparator_set=$(command printf "%s" "$semver" | command head -n1) - semver=$(command printf "%s" "$semver" | command tail -n +2) + comparator_set=$(command printf '%s' "$semver" | command head -n1) + semver=$(command printf '%s' "$semver" | command tail -n +2) [ -n "$comparator_set" ] || continue # convert comparators into required grammar - validated_comparator_set=$(command printf " %s " "$comparator_set" | + validated_comparator_set=$(command printf ' %s ' "$comparator_set" | command sed -E " # exactly 1 space is needed before and after every comparator (including the first and last comparators) s/\011/ /g;s/ +/ /g; @@ -473,10 +473,10 @@ nvm_normalize_semver() { fi done - validated_semver=$(command printf "%s" "$validated_semver" | command sed -E 's/^ \|\| //') + validated_semver=$(command printf '%s' "$validated_semver" | command sed -E 's/^ \|\| //') if nvm_is_valid_semver "$validated_semver"; then - command printf "%s" "$validated_semver" + command printf '%s' "$validated_semver" else return 1 fi @@ -498,7 +498,7 @@ nvm_interpret_complex_semver() { # - Resolve the comparator_set to its newest compatible version. # - Add the discovered newest compatible version to highest_compatible_versions. # - Choose the highest version among all the versions collected in highest_compatible_versions. - semver=$(command printf "%s" "$semver" | command tr '||' '\n') + semver=$(command printf '%s' "$semver" | command tr '||' '\n') local version_list_copy local current_comparator_set local current_version @@ -509,28 +509,28 @@ nvm_interpret_complex_semver() { highest_compatible_versions='' while [ -n "$semver" ]; do - version_list_copy=$(command printf "%s" "$version_list") - current_comparator_set=$(command printf "%s" "$semver" | command head -n1 | command sed -E 's/^ +//;s/ +$//') - semver=$(command printf "%s" "$semver" | command tail -n +2) + version_list_copy=$(command printf '%s' "$version_list") + current_comparator_set=$(command printf '%s' "$semver" | command head -n1 | command sed -E 's/^ +//;s/ +$//') + semver=$(command printf '%s' "$semver" | command tail -n +2) [ -n "$current_comparator_set" ] || continue # For each version in the version_list_copy (iterating from newest to oldest): # - If current_version satisfies all comparators in current_comparator_set, we've found the newest version compatible with all comparators in current current_comparator_set. # - Add discovered version to highest_compatible_versions and stop iterating through versions for current_comparator_set. while [ -n "$version_list_copy" ]; do - current_version=$(command printf "%s" "$version_list_copy" | command tail -n1 | command sed -E 's/^ +//;s/ +$//' | nvm_grep -o '^[0-9]\+\.[0-9]\+\.[0-9]\+$') - version_list_copy=$(command printf "%s" "$version_list_copy" | command sed '$d') + current_version=$(command printf '%s' "$version_list_copy" | command tail -n1 | command sed -E 's/^ +//;s/ +$//' | nvm_grep -o '^[0-9]\+\.[0-9]\+\.[0-9]\+$') + version_list_copy=$(command printf '%s' "$version_list_copy" | command sed '$d') [ -n "$current_version" ] || continue # For each comparator in the current_comparator_set_copy: # - If current_version is compatible with all comparators, we know current_version is the newest compatible version - current_comparator_set_copy=$(command printf "%s" "$current_comparator_set" | command tr ' ' '\n') + current_comparator_set_copy=$(command printf '%s' "$current_comparator_set" | command tr ' ' '\n') while [ -n "$current_comparator_set_copy" ]; do - current_comparator=$(command printf "%s" "$current_comparator_set_copy" | command head -n1 | command sed -E 's/^ +//;s/ +$//') - current_comparator_set_copy=$(command printf "%s" "$current_comparator_set_copy" | command tail -n +2) + current_comparator=$(command printf '%s' "$current_comparator_set_copy" | command head -n1 | command sed -E 's/^ +//;s/ +$//') + current_comparator_set_copy=$(command printf '%s' "$current_comparator_set_copy" | command tail -n +2) [ -n "$current_comparator" ] || continue - stripped_version_from_comparator=$(command printf "%s" "$current_comparator" | nvm_grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+$') + stripped_version_from_comparator=$(command printf '%s' "$current_comparator" | nvm_grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+$') if [ -z "$stripped_version_from_comparator" ]; then return 1 fi @@ -619,10 +619,10 @@ nvm_interpret_complex_semver() { local highest_compatible_version local compatible_node_version highest_compatible_version='0.0.0' - highest_compatible_versions=$(command printf "%s" "$highest_compatible_versions" | command tr ' ' '\n') + highest_compatible_versions=$(command printf '%s' "$highest_compatible_versions" | command tr ' ' '\n') while [ -n "$highest_compatible_versions" ]; do - compatible_node_version=$(command printf "%s" "$highest_compatible_versions" | command head -n1 | command sed -E 's/^ +//;s/ +$//') - highest_compatible_versions=$(command printf "%s" "$highest_compatible_versions" | command tail -n +2) + compatible_node_version=$(command printf '%s' "$highest_compatible_versions" | command head -n1 | command sed -E 's/^ +//;s/ +$//') + highest_compatible_versions=$(command printf '%s' "$highest_compatible_versions" | command tail -n +2) [ -n "$compatible_node_version" ] || continue if nvm_version_greater "$compatible_node_version" "$highest_compatible_version"; then @@ -630,7 +630,7 @@ nvm_interpret_complex_semver() { fi done if [ "$highest_compatible_version" != '0.0.0' ]; then - command printf "%s" "$highest_compatible_version" + command printf '%s' "$highest_compatible_version" else return 1 fi @@ -646,29 +646,29 @@ nvm_interpret_simple_semver() { return 1 fi local stripped_version_from_semver - stripped_version_from_semver=$(command printf "%s" "$semver" | nvm_grep -o '^[0-9]\+\.[0-9]\+\.[0-9]\+$') + stripped_version_from_semver=$(command printf '%s' "$semver" | nvm_grep -o '^[0-9]\+\.[0-9]\+\.[0-9]\+$') local newest_version_from_list - newest_version_from_list=$(command printf "%s" "$version_list" | tail -n 1 | nvm_grep -o '^[0-9]\+\.[0-9]\+\.[0-9]\+$') + newest_version_from_list=$(command printf '%s' "$version_list" | tail -n 1 | nvm_grep -o '^[0-9]\+\.[0-9]\+\.[0-9]\+$') if [ -z "$stripped_version_from_semver" ] || [ -z "$newest_version_from_list" ]; then return 1 fi local retrieved_version # if the semver is looking for an exact match, and it exists in the provided list of versions, resolve to that version if nvm_string_contains_regexp "$semver" '^[0-9]+\.[0-9]+\.[0-9]+$'; then - retrieved_version=$(command printf "%s" "$version_list" | nvm_grep "^$stripped_version_from_semver$") + retrieved_version=$(command printf '%s' "$version_list" | nvm_grep "^$stripped_version_from_semver$") if [ -n "$retrieved_version" ]; then - command printf "%s" "$retrieved_version" + command printf '%s' "$retrieved_version" return 0 else - # TODO we know it's not worth doing the complex semver interpratation at this point + command printf '%s' 'STOP' # we have determined no node version will be compatible with the semver return 1 fi # Semver is looking for the newest version that is <= to a sepcific version, and the version exists in the provided list of versions, resolve to that version elif nvm_string_contains_regexp "$semver" '^<=[0-9]+\.[0-9]+\.[0-9]+$'; then - retrieved_version=$(command printf "%s" "$version_list" | nvm_grep "^$stripped_version_from_semver$") + retrieved_version=$(command printf '%s' "$version_list" | nvm_grep "^$stripped_version_from_semver$") if [ -n "$retrieved_version" ]; then - command printf "%s" "$retrieved_version" + command printf '%s' "$retrieved_version" return 0 else return 1 # go on to try complex semver interpretation @@ -677,7 +677,7 @@ nvm_interpret_simple_semver() { # Semver is looking for the newest version >= a specific version, and the newest version in the provided list of versions is >= the specified version, resolve to that version. elif nvm_string_contains_regexp "$semver" '^>=[0-9]+\.[0-9]+\.[0-9]+$'; then if nvm_version_greater_than_or_equal_to "$newest_version_from_list" "$stripped_version_from_semver"; then - command printf "%s" "$newest_version_from_list" + command printf '%s' "$newest_version_from_list" return 0 else # TODO we know it's not worth doing the complex semver interpretation at this point @@ -686,10 +686,10 @@ nvm_interpret_simple_semver() { elif nvm_string_contains_regexp "$semver" '^>[0-9]+\.[0-9]+\.[0-9]+$'; then if nvm_version_greater "$newest_version_from_list" "$stripped_version_from_semver"; then - command printf "%s" "$newest_version_from_list" + command printf '%s' "$newest_version_from_list" return 0 else - # TODO we know it's not worth doing the complex semver interpretation at this point + command printf '%s' 'STOP' # we have determined no node version will be compatible with the semver return 1 fi @@ -720,19 +720,20 @@ nvm_interpret_node_semver() { return 1 fi - # TODO update nvm_interpret_simple_semver failure output to indicate if it is worth doing complex semver interpretation # If semver is a single comparator, use quick algorithm to determine newest compatible version local resolved_version resolved_version=$(nvm_interpret_simple_semver "$valid_transformed_semver" "$remote_node_versions") - if [ -n "$resolved_version" ]; then - command printf "%s" "$resolved_version" + if [ "$resolved_version" = 'STOP' ]; then + return 1 # nvm_interpret_simple_semver determined no node version will be compatible with the semver + elif [ -n "$resolved_version" ]; then + command printf '%s' "$resolved_version" return 0 fi # If semver is a semver with > 1 comparator, iterate through each remote node version from newest to oldest until finding the newest version compatible with all comparators. resolved_version=$(nvm_interpret_complex_semver "$valid_transformed_semver" "$remote_node_versions") if [ -n "$resolved_version" ]; then - command printf "%s" "$resolved_version" + command printf '%s' "$resolved_version" return 0 fi @@ -742,7 +743,7 @@ nvm_interpret_node_semver() { nvm_find_package_json() { dir="$(nvm_find_up 'package.json')" if [ -e "${dir}/package.json" ]; then - command printf "%s" "${dir}/package.json" + command printf '%s' "${dir}/package.json" fi } @@ -752,7 +753,7 @@ nvm_find_package_json() { # - semantic expression must match regexp: "[|<> [:alnum:].^=~*-]\+" nvm_get_node_from_pkg_json() { local package_json_contents - package_json_contents=${1-} + package_json_contents="${1-}" local engines_node_value engines_node_value='' local open_brackets @@ -768,7 +769,7 @@ nvm_get_node_from_pkg_json() { | while read -r i; do engines_node_value="$engines_node_value$i" if [ "$i" = '"' ]; then - if [ "$in_quotes" = 1 ]; then + if [ $in_quotes -eq 1 ]; then in_quotes=0 else in_quotes=1 @@ -776,7 +777,7 @@ nvm_get_node_from_pkg_json() { # spaces are interpretted as '' here but they need to be retained elif [ "$i" = '' ]; then engines_node_value="$engines_node_value " - elif [ "$in_quotes" = 1 ]; then + elif [ $in_quotes -eq 1 ]; then if [ "$i" = '{' ]; then open_brackets=$((open_brackets+1)) elif [ "$i" = '}' ]; then @@ -784,7 +785,7 @@ nvm_get_node_from_pkg_json() { fi fi if [ "$open_brackets" -ne 0 ] && [ "$open_brackets" -eq "$closed_brackets" ]; then - command printf "%s" "$engines_node_value" \ + command printf '%s' "$engines_node_value" \ | nvm_grep -o '"node": \?"[|<> [:alnum:].^=~*-]\+"' \ | command tr -d '"' \ | command awk -F: '{ print $2 }' \ From 8221df2a3f1451abfb675f94b876bfbb3f9f9141 Mon Sep 17 00:00:00 2001 From: edwmurph Date: Thu, 31 May 2018 18:46:54 -0400 Subject: [PATCH 11/12] renamed function; minor refactors; added test coverage; identified remaining work --- nvm.sh | 19 +++--- .../Unit tests/nvm_get_node_from_pkg_json | 61 +++++++++++-------- ..._valid_semver => nvm_is_normalized_semver} | 16 ++--- test/fast/Unit tests/nvm_normalize_semver | 50 ++++++++++++++- .../Unit tests/nvm_string_contains_regexp | 31 +++------- ...vm_trim_and_reduce_whitespace_to_one_space | 32 ++++++++-- test/generated_semvers.sh | 10 ++- test/slow/nvm_interpret_node_semver | 8 ++- 8 files changed, 152 insertions(+), 75 deletions(-) rename test/fast/Unit tests/{nvm_is_valid_semver => nvm_is_normalized_semver} (68%) diff --git a/nvm.sh b/nvm.sh index d632f56a42..6ddac0a842 100644 --- a/nvm.sh +++ b/nvm.sh @@ -317,23 +317,18 @@ nvm_string_contains_regexp() { # semver ::= comparator_set ( ' || ' comparator_set )* # comparator_set ::= comparator ( ' ' comparator )* # comparator ::= ( '<' | '<=' | '>' | '>=' | '' ) [0-9]+ '.' [0-9]+ '.' [0-9]+ -nvm_is_valid_semver() { +nvm_is_normalized_semver() { nvm_string_contains_regexp "${1-}" '^( ?(<|<=|>|>=)?[0-9]+\.[0-9]+\.[0-9]+)+( \|\| ( ?(<|<=|>|>=)?[0-9]+\.[0-9]+\.[0-9]+)+)*$' } nvm_trim_and_reduce_whitespace_to_one_space() { command printf '%s' "${1-}" | - command tr -d '\n\r' | - command tr '\t' ' ' | + command tr '\n\r\t\v\b' ' ' | command tr -s ' ' | command sed 's/^ //; s/ $//; s/^ //' } -# Attempts to normalize the given semver to the following grammar: -# -# semver ::= comparator_set ( ' || ' comparator_set )* -# comparator_set ::= comparator ( ' ' comparator )* -# comparator ::= ( '<' | '<=' | '>' | '>=' | '' ) [0-9]+ '.' [0-9]+ '.' [0-9]+ +# Attempts to normalize the given semver to the grammar defined with the function nvm_is_normalized_semver nvm_normalize_semver() { # split the semantic version's comparator_set's onto their own lines for iteration local semver @@ -475,7 +470,7 @@ nvm_normalize_semver() { validated_semver=$(command printf '%s' "$validated_semver" | command sed -E 's/^ \|\| //') - if nvm_is_valid_semver "$validated_semver"; then + if nvm_is_normalized_semver "$validated_semver"; then command printf '%s' "$validated_semver" else return 1 @@ -490,7 +485,7 @@ nvm_interpret_complex_semver() { semver="${1-}" local version_list version_list="${2-}" # expected to be sorted from oldest to newest - if [ -z "$semver" ] || [ -z "$version_list" ] || ! nvm_is_valid_semver "$semver"; then + if [ -z "$semver" ] || [ -z "$version_list" ] || ! nvm_is_normalized_semver "$semver"; then return 1 fi @@ -506,6 +501,7 @@ nvm_interpret_complex_semver() { local current_comparator local stripped_version_from_comparator local highest_compatible_versions + # TODO make this just always store the highest possible compatible version highest_compatible_versions='' while [ -n "$semver" ]; do @@ -521,6 +517,7 @@ nvm_interpret_complex_semver() { current_version=$(command printf '%s' "$version_list_copy" | command tail -n1 | command sed -E 's/^ +//;s/ +$//' | nvm_grep -o '^[0-9]\+\.[0-9]\+\.[0-9]\+$') version_list_copy=$(command printf '%s' "$version_list_copy" | command sed '$d') [ -n "$current_version" ] || continue + # TODO if current_version is less than the highest version in highest_compatile_versions, no need to continue # For each comparator in the current_comparator_set_copy: # - If current_version is compatible with all comparators, we know current_version is the newest compatible version @@ -4078,7 +4075,7 @@ nvm() { nvm_curl_libz_support nvm_command_info \ nvm_get_node_from_pkg_json nvm_find_package_json nvm_package_json_version \ nvm_interpret_node_semver nvm_interpret_simple_semver nvm_interpret_complex_semver nvm_normalize_semver \ - nvm_is_valid_semver nvm_string_contains_regexp nvm_trim_and_reduce_whitespace_to_one_space \ + nvm_is_normalized_semver nvm_string_contains_regexp nvm_trim_and_reduce_whitespace_to_one_space \ > /dev/null 2>&1 unset NVM_RC_VERSION NVM_NODEJS_ORG_MIRROR NVM_IOJS_ORG_MIRROR NVM_DIR \ NVM_CD_FLAGS NVM_BIN NVM_MAKE_JOBS \ diff --git a/test/fast/Unit tests/nvm_get_node_from_pkg_json b/test/fast/Unit tests/nvm_get_node_from_pkg_json index 163f293790..5f1f3eb6b1 100755 --- a/test/fast/Unit tests/nvm_get_node_from_pkg_json +++ b/test/fast/Unit tests/nvm_get_node_from_pkg_json @@ -8,21 +8,24 @@ # (TEST SET #1) uses valid TEST_SEMVER's and valid package.json templates -test_semvers_copy="$VALID_SEMVERS_FOR_PKG_JSON" +test_cases="$VALID_SEMVERS_FOR_PKG_JSON" +if [ -z "$test_cases" ]; then + die 'TEST SET 1 for nvm_get_node_from_pkg_json given an empty set of test cases' +fi prev_semver='' for template_file_name in package_json_templates/_valid_*; do - while [ -n "$test_semvers_copy" ]; do - semver=$(echo "$test_semvers_copy" | head -n1) - test_semvers_copy=$(echo "$test_semvers_copy" | tail -n +2) + while [ -n "$test_cases" ]; do + semver=$(echo "$test_cases" | head -n1) + test_cases=$(echo "$test_cases" | tail -n +2) [ -n "$semver" ] || continue expectedOutput=$(nvm_trim_and_reduce_whitespace_to_one_space "$semver") if [ "$prev_semver" = "$semver" ]; then - die "Problem iterating through test_semvers_copy (TEST SET #1). Encountered the same value twice in a row. prev_semver='$prev_semver' semver='$semver'.\n" + die "Problem iterating through test_cases (TEST SET #1). Encountered the same value twice in a row. prev_semver='$prev_semver' semver='$semver'.\n" fi prev_semver="$semver" - pkg_json_contents=$(sed 's/NODE_SEMVER/'"$semver"'/g' "$template_file_name" | tail -n +3) + pkg_json_contents=$(sed "s/NODE_SEMVER/$semver/g" "$template_file_name" | tail -n +3) actual_output=$(nvm_get_node_from_pkg_json "$pkg_json_contents") if [ "$actual_output" != "$expectedOutput" ] || [ -z "$actual_output" ] || [ -z "$pkg_json_contents" ]; then die "'nvm_get_node_from_pkg_json' POSITIVE test case failed (TEST SET #1). Expected '$expectedOutput' but got '$actual_output' when given input '$semver' and template '$template_file_name':\n$pkg_json_contents" @@ -35,20 +38,23 @@ done # (TEST SET #2) uses valid TEST_SEMVER's but invalid package.json templates -test_semvers_copy="$VALID_SEMVERS_FOR_PKG_JSON" +test_cases="$VALID_SEMVERS_FOR_PKG_JSON" +if [ -z "$test_cases" ]; then + die 'TEST SET 2 for nvm_get_node_from_pkg_json given an empty set of test cases' +fi prev_semver='' for template_file_name in package_json_templates/_invalid_*; do - while [ -n "$test_semvers_copy" ]; do - semver=$(echo "$test_semvers_copy" | head -n1) - test_semvers_copy=$(echo "$test_semvers_copy" | tail -n +2) + while [ -n "$test_cases" ]; do + semver=$(echo "$test_cases" | head -n1) + test_cases=$(echo "$test_cases" | tail -n +2) [ -n "$semver" ] || continue if [ "$prev_semver" = "$semver" ]; then - die "Problem iterating through test_semvers_copy (TEST SET #2). Encountered the same value twice in a row. prev_semver='$prev_semver' semver='$semver'.\n" + die "Problem iterating through test_cases (TEST SET #2). Encountered the same value twice in a row. prev_semver='$prev_semver' semver='$semver'.\n" fi prev_semver="$semver" - pkg_json_contents=$(sed 's/NODE_SEMVER/'"$semver"'/g' "$template_file_name" | tail -n +3) + pkg_json_contents=$(sed "s/NODE_SEMVER/$semver/g" "$template_file_name" | tail -n +3) actual_output=$(nvm_get_node_from_pkg_json "$pkg_json_contents") if [ "$actual_output" != "" ] || [ -z "$semver" ] || [ -z "$pkg_json_contents" ]; then die "'nvm_get_node_from_pkg_json' NEGATIVE test case failed (TEST SET #2). Expected to get empty string but got '$actual_output' when given input template '$template_file_name':\n$pkg_json_contents" @@ -58,20 +64,23 @@ done # (TEST SET #3) uses invalid TEST_SEMVER's but valid package.json templates -test_semvers_copy="$INVALID_SEMVERS_FOR_PKG_JSON" +test_cases="$INVALID_SEMVERS_FOR_PKG_JSON" +if [ -z "$test_cases" ]; then + die 'TEST SET 3 for nvm_get_node_from_pkg_json given an empty set of test cases' +fi prev_semver='' for template_file_name in package_json_templates/_valid_*; do - while [ -n "$test_semvers_copy" ]; do - semver=$(echo "$test_semvers_copy" | head -n1) + while [ -n "$test_cases" ]; do + semver=$(echo "$test_cases" | head -n1) [ -n "$semver" ] || continue - test_semvers_copy=$(echo "$test_semvers_copy" | tail -n +2) + test_cases=$(echo "$test_cases" | tail -n +2) if [ "$prev_semver" = "$semver" ]; then - die "Problem iterating through test_semvers_copy (TEST SET #3). Encountered the same value twice in a row. prev_semver='$prev_semver' semver='$semver'.\n" + die "Problem iterating through test_cases (TEST SET #3). Encountered the same value twice in a row. prev_semver='$prev_semver' semver='$semver'.\n" fi prev_semver=$(printf "%s" "$semver") - pkg_json_contents=$(sed 's/NODE_SEMVER/'"$semver"'/g' "$template_file_name" | tail -n +3) + pkg_json_contents=$(sed "s/NODE_SEMVER/$semver/g" "$template_file_name" | tail -n +3) actual_output=$(nvm_get_node_from_pkg_json "$pkg_json_contents") if [ "$actual_output" != "" ] || [ -z "$semver" ] || [ -z "$pkg_json_contents" ]; then die "'nvm_get_node_from_pkg_json' NEGATIVE test case failed (TEST SET #3). Expected to get empty string but got '$actual_output' when given input template '$template_file_name':\n$pkg_json_contents" @@ -80,24 +89,28 @@ for template_file_name in package_json_templates/_valid_*; do done # (TEST SET #4) uses invalid TEST_SEMVER's and invalid package.json templates -test_semvers_copy="$INVALID_SEMVERS_FOR_PKG_JSON" +test_cases="$INVALID_SEMVERS_FOR_PKG_JSON" +if [ -z "$test_cases" ]; then + die 'TEST SET 4 for nvm_get_node_from_pkg_json given an empty set of test cases' +fi prev_semver='' for template_file_name in package_json_templates/_invalid_*; do - while [ -n "$test_semvers_copy" ]; do - semver=$(echo "$test_semvers_copy" | head -n1) + while [ -n "$test_cases" ]; do + semver=$(echo "$test_cases" | head -n1) [ -n "$semver" ] || continue - test_semvers_copy=$(echo "$test_semvers_copy" | tail -n +2) + test_cases=$(echo "$test_cases" | tail -n +2) if [ "$prev_semver" = "$semver" ]; then - die "Problem iterating through test_semvers_copy (TEST SET #4). Encountered the same value twice in a row. prev_semver='$prev_semver' semver='$semver'.\n" + die "Problem iterating through test_cases (TEST SET #4). Encountered the same value twice in a row. prev_semver='$prev_semver' semver='$semver'.\n" fi prev_semver=$(printf "%s" "$semver") - pkg_json_contents=$(sed 's/NODE_SEMVER/'"$semver"'/g' "$template_file_name" | tail -n +3) + pkg_json_contents=$(sed "s/NODE_SEMVER/$semver/g" "$template_file_name" | tail -n +3) actual_output=$(nvm_get_node_from_pkg_json "$pkg_json_contents") if [ "$actual_output" != "" ] || [ -z "$semver" ] || [ -z "$pkg_json_contents" ]; then die "'nvm_get_node_from_pkg_json' NEGATIVE test case failed (TEST SET #4). Expected to get empty string but got '$actual_output' when given input template '$template_file_name':\n$pkg_json_contents" fi done done +exit 0 diff --git a/test/fast/Unit tests/nvm_is_valid_semver b/test/fast/Unit tests/nvm_is_normalized_semver similarity index 68% rename from test/fast/Unit tests/nvm_is_valid_semver rename to test/fast/Unit tests/nvm_is_normalized_semver index cba9cd1bf8..9e7d0fab5e 100755 --- a/test/fast/Unit tests/nvm_is_valid_semver +++ b/test/fast/Unit tests/nvm_is_normalized_semver @@ -3,7 +3,7 @@ \. ../../../nvm.sh \. ../../generated_semvers.sh -# nvm_is_valid_semver validates that given semvers adhere to the following grammer +# nvm_is_normalized_semver validates that given semvers adhere to the following grammer # # semver ::= comparator_set ( ' || ' comparator_set )* # comparator_set ::= comparator ( ' ' comparator )* @@ -13,14 +13,14 @@ positive_test_cases="$VALID_NORMALIZED_SEMVERS" if [ -z "$positive_test_cases" ]; then - die "positive test cases are empty" + die 'positive test cases are empty' fi prev_semver='' while [ -n "$positive_test_cases" ]; do semver=$(echo "$positive_test_cases" | head -n1) - if [ -z "$semver" ] || ! nvm_is_valid_semver "$semver"; then - die "nvm_string_contains_regexp POSITIVE test case failed. semver: '$semver'.\n" + if [ -z "$semver" ] || ! nvm_is_normalized_semver "$semver"; then + die "nvm_is_normalized_semver POSITIVE test case failed. semver: '$semver'.\n" fi if [ "$prev_semver" = "$semver" ]; then die "something is wrong. positive test cases received the same test case twice in a row. semver: '$semver'" @@ -31,16 +31,16 @@ done # NEGATIVE TEST CASES -negative_test_cases="$VALID_NON_NORMALIZED_SEMVERS" +negative_test_cases=$(printf "%s\n%s" "$VALID_NON_NORMALIZED_SEMVERS" "$INVALID_SEMVERS_FOR_PKG_JSON") if [ -z "$negative_test_cases" ]; then - die "negative test cases are empty" + die 'negative test cases are empty' fi prev_semver='initialized to non empty string' while [ -n "$negative_test_cases" ]; do semver=$(echo "$negative_test_cases" | head -n1) - if nvm_is_valid_semver "$semver"; then - die "nvm_string_contains_regexp NEGATIVE test case failed. semver: '$semver'.\n" + if nvm_is_normalized_semver "$semver"; then + die "nvm_is_normalized_semver NEGATIVE test case failed. semver: '$semver'.\n" fi if [ "$prev_semver" = "$semver" ]; then die "something is wrong. negative test cases received the same test case twice in a row. semver: '$semver'" diff --git a/test/fast/Unit tests/nvm_normalize_semver b/test/fast/Unit tests/nvm_normalize_semver index 8c7223b22a..e48b74a023 100755 --- a/test/fast/Unit tests/nvm_normalize_semver +++ b/test/fast/Unit tests/nvm_normalize_semver @@ -3,9 +3,36 @@ \. ../../../nvm.sh \. ../../generated_semvers.sh -# TODO add more test cases here -# Some test cases should just test that valid semvers from 'Shared test tesources'/semvers just produce some result +# TEST 1: Validate that for already normalized semvers, nvm_normalize_semver outputs the same semver. +test_cases="$VALID_NORMALIZED_SEMVERS" +if [ -z "$test_cases" ]; then + die 'nvm_normalize_semver (TEST 1) was given an empty set of test cases' +fi +while [ -n "$test_cases" ]; do + semver=$(echo "$test_cases" | head -n1) + actual_output=$(nvm_normalize_semver "$semver") + if [ -z "$semver" ] || [ "$semver" != "$actual_output" ]; then + die "nvm_normalize_semver (TEST 1) test case failed. Expected output: '$semver'. Actual output: '$actual_output'. Input: '$semver'.\n" + fi + test_cases=$(echo "$test_cases" | tail -n +2) +done + +# TEST 2: Validate that non normalized valid semvers produce a normalized result +test_cases="$VALID_NON_NORMALIZED_SEMVERS" +if [ -z "$test_cases" ]; then + die 'nvm_normalize_semver (TEST 2) was given an empty set of test cases' +fi +while [ -n "$test_cases" ]; do + semver=$(echo "$test_cases" | head -n1) + actual_output=$(nvm_normalize_semver "$semver") + if [ -z "$semver" ] || [ -z "$actual_output" ] || [ "$semver" = "$actual_output" ]; then + die "nvm_normalize_semver (TEST 2) test case failed. Expected output: '$semver'. Actual output: '$actual_output'. Input: '$semver'.\n" + fi + test_cases=$(echo "$test_cases" | tail -n +2) +done + +# TEST 3: Validate specific outputs for some specific inputs to nvm_normalize_semver. # input:expected_output test_cases="1.2.3:1.2.3 1.2.3 - 1.2.4:>=1.2.3 <=1.2.4 @@ -42,13 +69,30 @@ a || 1.2.3: ^x.1.1:>0.0.0 11.22.33:11.22.33" +if [ -z "$test_cases" ]; then + die 'nvm_normalize_semver (TEST 3) was given an empty set of test cases' +fi while [ -n "$test_cases" ]; do line=$(echo "$test_cases" | head -n1) input=$(echo "$line" | awk -F: '{ print $1 }') expected_output=$(echo "$line" | awk -F: '{ print $2 }') actual_output=$(nvm_normalize_semver "$input") if [ -z "$input" ] || [ "$actual_output" != "$expected_output" ]; then - die "nvm_normalize_semver test case failed. Expected output: '$expected_output'. Actual output: '$actual_output'. Input: '$input'.\n" + die "nvm_normalize_semver (TEST 3) test case failed. Expected output: '$expected_output'. Actual output: '$actual_output'. Input: '$input'.\n" + fi + test_cases=$(echo "$test_cases" | tail -n +2) +done + +# TEST 4: Validate that invalid semvers with invalid characters that shouldn't be retrieved from the package.json produce no result from nvm_normalize_semver +test_cases="$INVALID_SEMVERS_FOR_PKG_JSON" +if [ -z "$test_cases" ]; then + die 'nvm_normalize_semver (TEST 4) was given an empty set of test cases' +fi +while [ -n "$test_cases" ]; do + semver=$(echo "$test_cases" | head -n1) + actual_output=$(nvm_normalize_semver "$semver") + if [ -z "$semver" ] || [ -n "$actual_output" ]; then + die "nvm_normalize_semver (TEST 4) test case failed. Expected no output but got: '$actual_output'. Input: '$semver'.\n" fi test_cases=$(echo "$test_cases" | tail -n +2) done diff --git a/test/fast/Unit tests/nvm_string_contains_regexp b/test/fast/Unit tests/nvm_string_contains_regexp index a43f30af1d..495b8602ca 100755 --- a/test/fast/Unit tests/nvm_string_contains_regexp +++ b/test/fast/Unit tests/nvm_string_contains_regexp @@ -1,24 +1,17 @@ #!/bin/sh -die () { printf "$@" ; exit 1; } - \. ../../../nvm.sh +\. ../../generated_semvers.sh -valid_semver_regexp='^( ?(<|<=|>|>=|=|~|\^)?([0-9]+|x)\.([0-9]+|x)\.([0-9]+|x))+$' +normalized_semver_regexp='^( ?(<|<=|>|>=)?[0-9]+\.[0-9]+\.[0-9]+)+( \|\| ( ?(<|<=|>|>=)?[0-9]+\.[0-9]+\.[0-9]+)+)*$' # POSITIVE TEST CASES -# TODO add more test cases -# string:regexp -test_cases="1.2.3:$valid_semver_regexp -11.22.33:$valid_semver_regexp" - +test_cases="$VALID_NORMALIZED_SEMVERS" while [ -n "$test_cases" ]; do - line=$(echo "$test_cases" | head -n1) - string=$(echo "$line" | awk -F: '{ print $1 }') - regexp=$(echo "$line" | awk -F: '{ print $2 }') - if [ -z "$regexp" ] || [ -z "$string" ] || ! nvm_string_contains_regexp "$string" "$regexp"; then - die "nvm_string_contains_regexp POSITIVE test case failed. regexp: '$regexp'. string: '$string'.\n" + string=$(echo "$test_cases" | head -n1) + if [ -z "$normalized_semver_regexp" ] || [ -z "$string" ] || ! nvm_string_contains_regexp "$string" "$normalized_semver_regexp"; then + die "nvm_string_contains_regexp POSITIVE test case failed. regexp: '$normalized_semver_regexp'. string: '$string'.\n" fi test_cases=$(echo "$test_cases" | tail -n +2) done @@ -26,16 +19,12 @@ done # NEGATIVE TEST CASES # string:regexp -test_cases="1.2.a:$valid_semver_regexp -:$valid_semver_regexp -11.22.a:$valid_semver_regexp" +test_cases=$(printf "%s\n%s" "$VALID_NON_NORMALIZED_SEMVERS" "$INVALID_SEMVERS_FOR_PKG_JSON") while [ -n "$test_cases" ]; do - line=$(echo "$test_cases" | head -n1) - string=$(echo "$line" | awk -F: '{ print $1 }') - regexp=$(echo "$line" | awk -F: '{ print $2 }') - if [ -z "$regexp" ] || nvm_string_contains_regexp "$string" "$regexp"; then - die "nvm_string_contains_regexp NEGATIVE test case failed. regexp: '$regexp'. string: '$string'.\n" + string=$(echo "$test_cases" | head -n1) + if [ -z "$normalized_semver_regexp" ] || nvm_string_contains_regexp "$string" "$normalized_semver_regexp"; then + die "nvm_string_contains_regexp NEGATIVE test case failed. regexp: '$normalized_semver_regexp'. string: '$string'.\n" fi test_cases=$(echo "$test_cases" | tail -n +2) done diff --git a/test/fast/Unit tests/nvm_trim_and_reduce_whitespace_to_one_space b/test/fast/Unit tests/nvm_trim_and_reduce_whitespace_to_one_space index 52f5f0ae45..0e5da6f933 100755 --- a/test/fast/Unit tests/nvm_trim_and_reduce_whitespace_to_one_space +++ b/test/fast/Unit tests/nvm_trim_and_reduce_whitespace_to_one_space @@ -4,12 +4,11 @@ die () { printf "$@" ; exit 1; } \. ../../../nvm.sh -# TODO add more test cases +# Test cases that have no new lines # input:expected_output -test_cases="1.2.3:$valid_semver_regexp -11.22.33:$valid_semver_regexp" - test_cases='1:1 + 1 2 3:1 2 3 + 1.1 2.2 :1.1 2.2 1.2.3:1.2.3 1 1 :1 1 2 2 :2 2' @@ -24,5 +23,30 @@ while [ -n "$test_cases" ]; do fi test_cases=$(echo "$test_cases" | tail -n +2) done + +# Test cases that have new lines +expected_output='1 2 3' +new_line_1=' 1 + 2 3 ' +actual_output=$(nvm_trim_and_reduce_whitespace_to_one_space "$new_line_1") +if [ "$actual_output" != "$expected_output" ]; then + die "nvm_trim_and_reduce_whitespace_to_one_space test with new_line_1 failed. Actual output: '$actual_output' Expected output: '$expected_output'" +fi + +new_line_2=' 1 2 +3 ' +actual_output=$(nvm_trim_and_reduce_whitespace_to_one_space "$new_line_2") +if [ "$actual_output" != "$expected_output" ]; then + die "nvm_trim_and_reduce_whitespace_to_one_space test with new_line_2 failed. Actual output: '$actual_output' Expected output: '$expected_output'" +fi + +new_line_3=' 1 + +2 +3' +actual_output=$(nvm_trim_and_reduce_whitespace_to_one_space "$new_line_3") +if [ "$actual_output" != "$expected_output" ]; then + die "nvm_trim_and_reduce_whitespace_to_one_space test with new_line_3 failed. Actual output: '$actual_output' Expected output: '$expected_output'" +fi exit 0 diff --git a/test/generated_semvers.sh b/test/generated_semvers.sh index 9f1f8916a6..7ea9384c3e 100755 --- a/test/generated_semvers.sh +++ b/test/generated_semvers.sh @@ -96,7 +96,7 @@ VALID_NORMALIZED_COMPLEX_SEMVERS='10.3.0 || 8.1.1 || 4.1.0 8.0.0 || <6.12.0' # Valid semvers that should resolve to a node version but need to be validated/normalized before interpretting. -VALID_NON_NORMALIZED_SEMVERS='x +VALID_NON_NORMALIZED_COMPLEX_SEMVERS='x X * x.x @@ -175,10 +175,14 @@ VALID_NORMALIZED_SEMVERS=$(printf "%s\n%s\n%s" \ "$(generate_semvers "$VALID_NORMALIZED_VERSIONS" "$VALID_NORMALIZED_SEMVER_OPERATORS")" \ ) -VALID_SEMVERS=$(printf "%s\n%s\n%s" \ +VALID_NON_NORMALIZED_SEMVERS=$(printf "%s\n%s" \ + "$VALID_NON_NORMALIZED_COMPLEX_SEMVERS" \ + "$(generate_semvers "$VALID_NORMALIZED_VERSIONS" "$VALID_NON_NORMALIZED_SEMVER_OPERATORS" 0)" \ +) + +VALID_SEMVERS=$(printf "%s\n%s" \ "$VALID_NORMALIZED_SEMVERS" \ "$VALID_NON_NORMALIZED_SEMVERS" \ - "$(generate_semvers "$VALID_NORMALIZED_VERSIONS" "$VALID_NON_NORMALIZED_SEMVER_OPERATORS" 0)" \ ) VALID_SEMVERS_FOR_PKG_JSON=$(printf "%s\n%s" \ diff --git a/test/slow/nvm_interpret_node_semver b/test/slow/nvm_interpret_node_semver index c9b484fe0a..379d9233ae 100755 --- a/test/slow/nvm_interpret_node_semver +++ b/test/slow/nvm_interpret_node_semver @@ -30,6 +30,7 @@ while [ -n "$test_cases" ]; do done # TODO add more test cases here +# TODO get the following semvers working: ^7 ^7.x ^7.0 ~8 ~8.0 # Verify actual outputs given some inputs # input:expected_output test_cases="*:$NEWEST_NODE_VERSION @@ -38,7 +39,12 @@ x:$NEWEST_NODE_VERSION X:$NEWEST_NODE_VERSION 0.12.18:0.12.18 0.11.16:0.11.16 -222.22.2: +7.0.0||7.2.1:7.2.1 +7-8:8.0.0 +7.0:7.0.0 +^7.0.0:7.10.1 +~8.1.0:8.1.4 +^7.0.0||6.8.1:7.10.1 >0.12.18:$NEWEST_NODE_VERSION >=0.11.16:$NEWEST_NODE_VERSION 7.1.0 || 7.3.0 || 7.2.0:7.3.0 From 3e2eddd05a7cb61f07104a4180c2849c38b1c894 Mon Sep 17 00:00:00 2001 From: edwmurph Date: Thu, 31 May 2018 22:47:05 -0400 Subject: [PATCH 12/12] decent optimization; added last of test cases; fixed corner case; added some polish --- nvm.sh | 77 +++++++++---------- .../Unit tests/nvm_get_node_from_pkg_json | 52 +++++++------ test/fast/Unit tests/nvm_is_normalized_semver | 10 +-- test/fast/Unit tests/nvm_normalize_semver | 2 + .../Unit tests/nvm_string_contains_regexp | 6 +- ...vm_trim_and_reduce_whitespace_to_one_space | 21 ++--- test/generated_semvers.sh | 21 +++-- test/slow/nvm_interpret_node_semver | 13 +++- 8 files changed, 102 insertions(+), 100 deletions(-) diff --git a/nvm.sh b/nvm.sh index 6ddac0a842..d624be087d 100644 --- a/nvm.sh +++ b/nvm.sh @@ -358,13 +358,13 @@ nvm_normalize_semver() { s/ ?- ?/ - /g; # ' 1 ' => ' 1.x.x ' - # ' x ' => ' x.x.x ' - s/ ([0-9]+|x) / \1.x.x /g; + # ' ~x ' => ' ~x.x.x ' + s/ (~|\^)?([0-9]+|x) / \1\2.x.x /g; # ' 1.2 ' => ' 1.2.x ' - # ' 1.x ' => ' 1.x.x ' - # ' x.x ' => ' x.x.x ' - s/ (([0-9]+|x)\.([0-9]+|x)) / \1.x /g; + # ' ~1.x ' => ' ~1.x.x ' + # ' ^x.x ' => ' ^x.x.x ' + s/ (~|\^)?(([0-9]+|x)\.([0-9]+|x)) / \1\2.x /g; # ' 1.2.3 - 1.2.4 ' => ' >=1.2.3 <=1.2.4 ' s/ (([0-9]+|x)\.([0-9]+|x)\.([0-9]+|x)) ?\- ?(([0-9]+|x)\.([0-9]+|x)\.([0-9]+|x)) / >=\1 <=\5 /g; @@ -387,9 +387,11 @@ nvm_normalize_semver() { # ` ^0.0.1 ` => ` >=0.0.1 <0.0.2 ` # ` ^0.1.2 ` => ` >=0.1.2 <0.2.0 ` # ` ^1.2.3 ` => ` >=1.2.3 <2.0.0 ` + # ` ^1.0.0 ` => ` >=1.0.0 <2.0.0 ` # ` ~0.0.1 ` => ` >=0.0.1 <0.1.0 ` # ` ~0.1.2 ` => ` >=0.1.2 <0.2.0 ` # ` ~1.2.3 ` => ` >=1.2.3 <1.3.0 ` + # ` ~1.0.0 ` => ` >=1.0.0 <2.0.0 ` # ` x.x.x ` => ` >0.0.0 ` # ` x.x.1 ` => ` >0.0.0 ` # ` x.1.x ` => ` >0.0.0 ` @@ -438,7 +440,11 @@ nvm_normalize_semver() { output=output ">=" version " <" a[1]+1 ".0.0 "; } } else if ( match(comparator, /^~/) ) { - if ( match(comparator, /^~0.0.[0-9]+$/) ) { + if ( match(comparator, /^~[0-9]+.0.0$/) ) { + version=substr(comparator,2) + split(version, a, /\./) + output=output ">=" version " <" a[1]+1 ".0.0 " + } else if ( match(comparator, /^~0.0.[0-9]+$/) ) { version=substr(comparator,2) split(version, a, /\./) output=output ">=" version " <0." a[2]+1 ".0 " @@ -472,9 +478,9 @@ nvm_normalize_semver() { if nvm_is_normalized_semver "$validated_semver"; then command printf '%s' "$validated_semver" - else - return 1 + return 0 fi + return 1 } # Given a semver and version list, find the highest compatible version by doing the following: @@ -490,9 +496,9 @@ nvm_interpret_complex_semver() { fi # For each comparator_set in the semver: - # - Resolve the comparator_set to its newest compatible version. - # - Add the discovered newest compatible version to highest_compatible_versions. - # - Choose the highest version among all the versions collected in highest_compatible_versions. + # - Try to resolve the comparator_set to its newest compatible version. + # - But stop looking for a compatible version for a comparator_set if the current_version being iterated on is lower than an already found compatible version. + # - If by the end of the algorithm, a version other than 0.0.0 is collected in highest_compatible_version, output that version. semver=$(command printf '%s' "$semver" | command tr '||' '\n') local version_list_copy local current_comparator_set @@ -500,9 +506,8 @@ nvm_interpret_complex_semver() { local current_comparator_set_copy local current_comparator local stripped_version_from_comparator - local highest_compatible_versions - # TODO make this just always store the highest possible compatible version - highest_compatible_versions='' + local highest_compatible_version + highest_compatible_version='0.0.0' while [ -n "$semver" ]; do version_list_copy=$(command printf '%s' "$version_list") @@ -511,13 +516,17 @@ nvm_interpret_complex_semver() { [ -n "$current_comparator_set" ] || continue # For each version in the version_list_copy (iterating from newest to oldest): - # - If current_version satisfies all comparators in current_comparator_set, we've found the newest version compatible with all comparators in current current_comparator_set. - # - Add discovered version to highest_compatible_versions and stop iterating through versions for current_comparator_set. + # - If current_version satisfies all comparators in current_comparator_set, we've found the newest highest version compatible with all comparators in current current_comparator_set. + # - Store the current_version in highest_compatible_version and stop iterating through versions for current_comparator_set. while [ -n "$version_list_copy" ]; do current_version=$(command printf '%s' "$version_list_copy" | command tail -n1 | command sed -E 's/^ +//;s/ +$//' | nvm_grep -o '^[0-9]\+\.[0-9]\+\.[0-9]\+$') version_list_copy=$(command printf '%s' "$version_list_copy" | command sed '$d') [ -n "$current_version" ] || continue - # TODO if current_version is less than the highest version in highest_compatile_versions, no need to continue + if [ "$highest_compatible_version" != '0.0.0' ] && nvm_version_greater "$highest_compatible_version" "$current_version"; then + # If we previously found a compatible version that is higher than the current_version, there is no need to continue checking versions. + version_list_copy='' + continue + fi # For each comparator in the current_comparator_set_copy: # - If current_version is compatible with all comparators, we know current_version is the newest compatible version @@ -537,7 +546,7 @@ nvm_interpret_complex_semver() { # current_comparator is looking for an exact match, and the current_version is the exact match, so this current_comparator is satisfied. if [ -z "$current_comparator_set_copy" ]; then # Also, this is the last comparator in the current_comparator_set_copy so we can assume we've found the newest compatible version of the current_comparator_set. - highest_compatible_versions="$highest_compatible_versions $current_version" + highest_compatible_version="$current_version" version_list_copy='' fi elif nvm_version_greater "$stripped_version_from_comparator" "$current_version"; then @@ -554,7 +563,7 @@ nvm_interpret_complex_semver() { # current_version is less than or equal to the current_comparator version number so this current_comparator is satisfied. if [ -z "$current_comparator_set_copy" ]; then # Also, this is the last comparator in the current_comparator_set_copy so we can assume we've found the newest compatible version of the current_comparator_set. - highest_compatible_versions="$highest_compatible_versions $current_version" + highest_compatible_version="$current_version" version_list_copy='' fi else @@ -567,7 +576,7 @@ nvm_interpret_complex_semver() { # current_version is greater than or equal to the current_comparator version number so this current_comparator is satisfied. if [ -z "$current_comparator_set_copy" ]; then # Also, this is the last comparator in the current_comparator_set_copy so we can assume we've found the newest compatible version of the current_comparator_set. - highest_compatible_versions="$highest_compatible_versions $current_version" + highest_compatible_version="$current_version" version_list_copy='' fi else @@ -581,7 +590,7 @@ nvm_interpret_complex_semver() { # current_version is less than the current_comparator version number so this current_comparator is satisfied. if [ -z "$current_comparator_set_copy" ]; then # Also, this is the last comparator in the current_comparator_set_copy so we can assume we've found the newest compatible version of the current_comparator_set. - highest_compatible_versions="$highest_compatible_versions $current_version" + highest_compatible_version="$current_version" version_list_copy='' fi else @@ -594,7 +603,7 @@ nvm_interpret_complex_semver() { # current_version is greater than the current_comparator version number so this current_comparator is satisfied. if [ -z "$current_comparator_set_copy" ];then # Also, this is the last comparator in the current_comparator_set_copy so we can assume we've found the newest compatible version of the current_comparator_set. - highest_compatible_versions="$highest_compatible_versions $current_version" + highest_compatible_version="$current_version" version_list_copy='' fi else @@ -610,27 +619,11 @@ nvm_interpret_complex_semver() { done # while [ -n "$version_list_copy" ]; do done # while [ -n "$semver" ]; do - # Iterate through each of the versions in highest_compatible_versions, which are the highest versions that satisfy each of the comparator sets. - # Since comparator sets are separated by '||', choosing any of the highest versions compatible with any of the comparator_sets would be compatible with the whole semver. - # Therefore, we should resolve to the highest version in highest_compatible_versions. - local highest_compatible_version - local compatible_node_version - highest_compatible_version='0.0.0' - highest_compatible_versions=$(command printf '%s' "$highest_compatible_versions" | command tr ' ' '\n') - while [ -n "$highest_compatible_versions" ]; do - compatible_node_version=$(command printf '%s' "$highest_compatible_versions" | command head -n1 | command sed -E 's/^ +//;s/ +$//') - highest_compatible_versions=$(command printf '%s' "$highest_compatible_versions" | command tail -n +2) - [ -n "$compatible_node_version" ] || continue - - if nvm_version_greater "$compatible_node_version" "$highest_compatible_version"; then - highest_compatible_version="$compatible_node_version" - fi - done - if [ "$highest_compatible_version" != '0.0.0' ]; then + if [ -n "$highest_compatible_version" ] && [ "$highest_compatible_version" != '0.0.0' ]; then command printf '%s' "$highest_compatible_version" - else - return 1 + return 0 fi + return 1 } # Given a semver and version list, optimize discovery of highest compatible version with this function which quickly interprets some common semvers. @@ -677,7 +670,7 @@ nvm_interpret_simple_semver() { command printf '%s' "$newest_version_from_list" return 0 else - # TODO we know it's not worth doing the complex semver interpretation at this point + command printf '%s' 'STOP' # we have determined no node version will be compatible with the semver return 1 fi diff --git a/test/fast/Unit tests/nvm_get_node_from_pkg_json b/test/fast/Unit tests/nvm_get_node_from_pkg_json index 5f1f3eb6b1..1bef708399 100755 --- a/test/fast/Unit tests/nvm_get_node_from_pkg_json +++ b/test/fast/Unit tests/nvm_get_node_from_pkg_json @@ -3,14 +3,14 @@ \. ../../../nvm.sh \. ../../generated_semvers.sh +# This test suite validates the behavior of extracting the semver from a package.json's engine.node value given valid/invalid semvers and valid/invalid json structures. -# POSITIVE TEST CASES - - -# (TEST SET #1) uses valid TEST_SEMVER's and valid package.json templates +# (TEST 1 POSITIVE TEST CASES) +# SEMVERS: valid +# PACKAGE.JSON TEMPLATES: invalid test_cases="$VALID_SEMVERS_FOR_PKG_JSON" if [ -z "$test_cases" ]; then - die 'TEST SET 1 for nvm_get_node_from_pkg_json given an empty set of test cases' + die 'TEST 1 for nvm_get_node_from_pkg_json given an empty set of test cases' fi prev_semver='' for template_file_name in package_json_templates/_valid_*; do @@ -21,26 +21,24 @@ for template_file_name in package_json_templates/_valid_*; do expectedOutput=$(nvm_trim_and_reduce_whitespace_to_one_space "$semver") if [ "$prev_semver" = "$semver" ]; then - die "Problem iterating through test_cases (TEST SET #1). Encountered the same value twice in a row. prev_semver='$prev_semver' semver='$semver'.\n" + die "Problem iterating through test_cases (TEST 1). Encountered the same value twice in a row. prev_semver='$prev_semver' semver='$semver'.\n" fi prev_semver="$semver" pkg_json_contents=$(sed "s/NODE_SEMVER/$semver/g" "$template_file_name" | tail -n +3) actual_output=$(nvm_get_node_from_pkg_json "$pkg_json_contents") if [ "$actual_output" != "$expectedOutput" ] || [ -z "$actual_output" ] || [ -z "$pkg_json_contents" ]; then - die "'nvm_get_node_from_pkg_json' POSITIVE test case failed (TEST SET #1). Expected '$expectedOutput' but got '$actual_output' when given input '$semver' and template '$template_file_name':\n$pkg_json_contents" + die "'nvm_get_node_from_pkg_json' POSITIVE test case failed (TEST 1). Expected '$expectedOutput' but got '$actual_output' when given input '$semver' and template '$template_file_name':\n$pkg_json_contents" fi done done - -# NEGATIVE TEST CASES - - -# (TEST SET #2) uses valid TEST_SEMVER's but invalid package.json templates +# (TEST 2 NEGATIVE TEST CASES) +# SEMVERS: valid +# PACKAGE.JSON TEMPLATES: invalid test_cases="$VALID_SEMVERS_FOR_PKG_JSON" if [ -z "$test_cases" ]; then - die 'TEST SET 2 for nvm_get_node_from_pkg_json given an empty set of test cases' + die 'TEST 2 for nvm_get_node_from_pkg_json given an empty set of test cases' fi prev_semver='' for template_file_name in package_json_templates/_invalid_*; do @@ -50,23 +48,25 @@ for template_file_name in package_json_templates/_invalid_*; do [ -n "$semver" ] || continue if [ "$prev_semver" = "$semver" ]; then - die "Problem iterating through test_cases (TEST SET #2). Encountered the same value twice in a row. prev_semver='$prev_semver' semver='$semver'.\n" + die "Problem iterating through test_cases (TEST 2). Encountered the same value twice in a row. prev_semver='$prev_semver' semver='$semver'.\n" fi prev_semver="$semver" pkg_json_contents=$(sed "s/NODE_SEMVER/$semver/g" "$template_file_name" | tail -n +3) actual_output=$(nvm_get_node_from_pkg_json "$pkg_json_contents") if [ "$actual_output" != "" ] || [ -z "$semver" ] || [ -z "$pkg_json_contents" ]; then - die "'nvm_get_node_from_pkg_json' NEGATIVE test case failed (TEST SET #2). Expected to get empty string but got '$actual_output' when given input template '$template_file_name':\n$pkg_json_contents" + die "'nvm_get_node_from_pkg_json' NEGATIVE test case failed (TEST 2). Expected to get empty string but got '$actual_output' when given input template '$template_file_name':\n$pkg_json_contents" fi done done -# (TEST SET #3) uses invalid TEST_SEMVER's but valid package.json templates +# (TEST 3 NEGATIVE TEST CASES) +# SEMVERS: invalid +# PACKAGE.JSON TEMPLATES: valid test_cases="$INVALID_SEMVERS_FOR_PKG_JSON" if [ -z "$test_cases" ]; then - die 'TEST SET 3 for nvm_get_node_from_pkg_json given an empty set of test cases' + die 'TEST 3 for nvm_get_node_from_pkg_json given an empty set of test cases' fi prev_semver='' for template_file_name in package_json_templates/_valid_*; do @@ -76,22 +76,24 @@ for template_file_name in package_json_templates/_valid_*; do test_cases=$(echo "$test_cases" | tail -n +2) if [ "$prev_semver" = "$semver" ]; then - die "Problem iterating through test_cases (TEST SET #3). Encountered the same value twice in a row. prev_semver='$prev_semver' semver='$semver'.\n" + die "Problem iterating through test_cases (TEST 3). Encountered the same value twice in a row. prev_semver='$prev_semver' semver='$semver'.\n" fi - prev_semver=$(printf "%s" "$semver") + prev_semver="$semver" pkg_json_contents=$(sed "s/NODE_SEMVER/$semver/g" "$template_file_name" | tail -n +3) actual_output=$(nvm_get_node_from_pkg_json "$pkg_json_contents") if [ "$actual_output" != "" ] || [ -z "$semver" ] || [ -z "$pkg_json_contents" ]; then - die "'nvm_get_node_from_pkg_json' NEGATIVE test case failed (TEST SET #3). Expected to get empty string but got '$actual_output' when given input template '$template_file_name':\n$pkg_json_contents" + die "'nvm_get_node_from_pkg_json' NEGATIVE test case failed (TEST 3). Expected to get empty string but got '$actual_output' when given input template '$template_file_name':\n$pkg_json_contents" fi done done -# (TEST SET #4) uses invalid TEST_SEMVER's and invalid package.json templates +# (TEST 4 NEGATIVE TEST CASES) +# SEMVERS: invalid +# PACKAGE.JSON TEMPLATES: invalid test_cases="$INVALID_SEMVERS_FOR_PKG_JSON" if [ -z "$test_cases" ]; then - die 'TEST SET 4 for nvm_get_node_from_pkg_json given an empty set of test cases' + die 'TEST 4 for nvm_get_node_from_pkg_json given an empty set of test cases' fi prev_semver='' for template_file_name in package_json_templates/_invalid_*; do @@ -101,14 +103,14 @@ for template_file_name in package_json_templates/_invalid_*; do test_cases=$(echo "$test_cases" | tail -n +2) if [ "$prev_semver" = "$semver" ]; then - die "Problem iterating through test_cases (TEST SET #4). Encountered the same value twice in a row. prev_semver='$prev_semver' semver='$semver'.\n" + die "Problem iterating through test_cases (TEST 4). Encountered the same value twice in a row. prev_semver='$prev_semver' semver='$semver'.\n" fi - prev_semver=$(printf "%s" "$semver") + prev_semver="$semver" pkg_json_contents=$(sed "s/NODE_SEMVER/$semver/g" "$template_file_name" | tail -n +3) actual_output=$(nvm_get_node_from_pkg_json "$pkg_json_contents") if [ "$actual_output" != "" ] || [ -z "$semver" ] || [ -z "$pkg_json_contents" ]; then - die "'nvm_get_node_from_pkg_json' NEGATIVE test case failed (TEST SET #4). Expected to get empty string but got '$actual_output' when given input template '$template_file_name':\n$pkg_json_contents" + die "'nvm_get_node_from_pkg_json' NEGATIVE test case failed (TEST 4). Expected to get empty string but got '$actual_output' when given input template '$template_file_name':\n$pkg_json_contents" fi done done diff --git a/test/fast/Unit tests/nvm_is_normalized_semver b/test/fast/Unit tests/nvm_is_normalized_semver index 9e7d0fab5e..2be8cff5ee 100755 --- a/test/fast/Unit tests/nvm_is_normalized_semver +++ b/test/fast/Unit tests/nvm_is_normalized_semver @@ -3,19 +3,13 @@ \. ../../../nvm.sh \. ../../generated_semvers.sh -# nvm_is_normalized_semver validates that given semvers adhere to the following grammer -# -# semver ::= comparator_set ( ' || ' comparator_set )* -# comparator_set ::= comparator ( ' ' comparator )* -# comparator ::= ( '<' | '<=' | '>' | '>=' | '' ) [0-9]+ '.' [0-9]+ '.' [0-9]+ +# nvm_is_normalized_semver validates that a given semver adheres to a particular grammar. See grammar with definition of function nvm_is_normalized_semver() in nvm.sh. # POSITIVE TEST CASES - positive_test_cases="$VALID_NORMALIZED_SEMVERS" if [ -z "$positive_test_cases" ]; then die 'positive test cases are empty' fi - prev_semver='' while [ -n "$positive_test_cases" ]; do semver=$(echo "$positive_test_cases" | head -n1) @@ -30,12 +24,10 @@ while [ -n "$positive_test_cases" ]; do done # NEGATIVE TEST CASES - negative_test_cases=$(printf "%s\n%s" "$VALID_NON_NORMALIZED_SEMVERS" "$INVALID_SEMVERS_FOR_PKG_JSON") if [ -z "$negative_test_cases" ]; then die 'negative test cases are empty' fi - prev_semver='initialized to non empty string' while [ -n "$negative_test_cases" ]; do semver=$(echo "$negative_test_cases" | head -n1) diff --git a/test/fast/Unit tests/nvm_normalize_semver b/test/fast/Unit tests/nvm_normalize_semver index e48b74a023..b68da178c1 100755 --- a/test/fast/Unit tests/nvm_normalize_semver +++ b/test/fast/Unit tests/nvm_normalize_semver @@ -3,6 +3,8 @@ \. ../../../nvm.sh \. ../../generated_semvers.sh +# This test suite validates the behavior of normalizing a semver from its raw form to a specific grammar. See the grammar defined with nvm_is_normalized_semver() in nvm.sh. + # TEST 1: Validate that for already normalized semvers, nvm_normalize_semver outputs the same semver. test_cases="$VALID_NORMALIZED_SEMVERS" if [ -z "$test_cases" ]; then diff --git a/test/fast/Unit tests/nvm_string_contains_regexp b/test/fast/Unit tests/nvm_string_contains_regexp index 495b8602ca..b3116cfb20 100755 --- a/test/fast/Unit tests/nvm_string_contains_regexp +++ b/test/fast/Unit tests/nvm_string_contains_regexp @@ -2,11 +2,11 @@ \. ../../../nvm.sh \. ../../generated_semvers.sh - normalized_semver_regexp='^( ?(<|<=|>|>=)?[0-9]+\.[0-9]+\.[0-9]+)+( \|\| ( ?(<|<=|>|>=)?[0-9]+\.[0-9]+\.[0-9]+)+)*$' -# POSITIVE TEST CASES +# Validates the behavior of nvm_string_contains_regexp() which returns 0 if the given string contains the given regular expression. +# POSITIVE TEST CASES test_cases="$VALID_NORMALIZED_SEMVERS" while [ -n "$test_cases" ]; do string=$(echo "$test_cases" | head -n1) @@ -17,10 +17,8 @@ while [ -n "$test_cases" ]; do done # NEGATIVE TEST CASES - # string:regexp test_cases=$(printf "%s\n%s" "$VALID_NON_NORMALIZED_SEMVERS" "$INVALID_SEMVERS_FOR_PKG_JSON") - while [ -n "$test_cases" ]; do string=$(echo "$test_cases" | head -n1) if [ -z "$normalized_semver_regexp" ] || nvm_string_contains_regexp "$string" "$normalized_semver_regexp"; then diff --git a/test/fast/Unit tests/nvm_trim_and_reduce_whitespace_to_one_space b/test/fast/Unit tests/nvm_trim_and_reduce_whitespace_to_one_space index 0e5da6f933..358a5f6a31 100755 --- a/test/fast/Unit tests/nvm_trim_and_reduce_whitespace_to_one_space +++ b/test/fast/Unit tests/nvm_trim_and_reduce_whitespace_to_one_space @@ -1,8 +1,9 @@ #!/bin/sh +\. ../../../nvm.sh die () { printf "$@" ; exit 1; } -\. ../../../nvm.sh +# Validates the behavior of nvm_trim_and_reduce_whitespace_to_one_space() which consolidates all consecutive white space to one space, and then trims any spaces from the ends. # Test cases that have no new lines # input:expected_output @@ -26,27 +27,27 @@ done # Test cases that have new lines expected_output='1 2 3' -new_line_1=' 1 +test_case_with_new_line_1=' 1 2 3 ' -actual_output=$(nvm_trim_and_reduce_whitespace_to_one_space "$new_line_1") +actual_output=$(nvm_trim_and_reduce_whitespace_to_one_space "$test_case_with_new_line_1") if [ "$actual_output" != "$expected_output" ]; then - die "nvm_trim_and_reduce_whitespace_to_one_space test with new_line_1 failed. Actual output: '$actual_output' Expected output: '$expected_output'" + die "nvm_trim_and_reduce_whitespace_to_one_space test with test_case_with_new_line_1 failed. Actual output: '$actual_output' Expected output: '$expected_output'" fi -new_line_2=' 1 2 +test_case_with_new_line_2=' 1 2 3 ' -actual_output=$(nvm_trim_and_reduce_whitespace_to_one_space "$new_line_2") +actual_output=$(nvm_trim_and_reduce_whitespace_to_one_space "$test_case_with_new_line_2") if [ "$actual_output" != "$expected_output" ]; then - die "nvm_trim_and_reduce_whitespace_to_one_space test with new_line_2 failed. Actual output: '$actual_output' Expected output: '$expected_output'" + die "nvm_trim_and_reduce_whitespace_to_one_space test with test_case_with_new_line_2 failed. Actual output: '$actual_output' Expected output: '$expected_output'" fi -new_line_3=' 1 +test_case_with_new_line_3=' 1 2 3' -actual_output=$(nvm_trim_and_reduce_whitespace_to_one_space "$new_line_3") +actual_output=$(nvm_trim_and_reduce_whitespace_to_one_space "$test_case_with_new_line_3") if [ "$actual_output" != "$expected_output" ]; then - die "nvm_trim_and_reduce_whitespace_to_one_space test with new_line_3 failed. Actual output: '$actual_output' Expected output: '$expected_output'" + die "nvm_trim_and_reduce_whitespace_to_one_space test with test_case_with_new_line_3 failed. Actual output: '$actual_output' Expected output: '$expected_output'" fi exit 0 diff --git a/test/generated_semvers.sh b/test/generated_semvers.sh index 7ea9384c3e..422f11b045 100755 --- a/test/generated_semvers.sh +++ b/test/generated_semvers.sh @@ -18,7 +18,7 @@ VALID_NON_NORMALIZED_SEMVER_OPERATORS='v # Versions (stripped of any operators) that are considered valid inputs to the semver interpretation logic. VALID_NORMALIZED_VERSIONS='4.1.0 0.12.18 -0.11.16 +8.0.0 6.11.4 10.0.0' @@ -62,8 +62,6 @@ asdf 1111 1 1 1. -1.1 -1.* 1.2 11.222 1.2.a @@ -71,8 +69,6 @@ asdf 1.x.x 11.22.a =1.2.3 -~1.2.3 -^1.2.3 1.1.1 2.2.2 >1.1.1 <1.1.0 1.2 - 1.3 @@ -81,9 +77,7 @@ asdf 1.2.3 - 1.2.4 1.2.3-1.2.4 1.2 1.3 -1 2 -1.2.3||1.2.4 -1.2||1.3 +10 20 1||2 >1000 <0" @@ -97,12 +91,23 @@ VALID_NORMALIZED_COMPLEX_SEMVERS='10.3.0 || 8.1.1 || 4.1.0 # Valid semvers that should resolve to a node version but need to be validated/normalized before interpretting. VALID_NON_NORMALIZED_COMPLEX_SEMVERS='x +10 +~10 +^10 X * x.x X.X *.* +10.x +~10.x +^10.x +10.X.X +~10.X.X +^10.X.X x.x.x +~X.X.X +^X.X.X X.X.X x.X.* *.x.X diff --git a/test/slow/nvm_interpret_node_semver b/test/slow/nvm_interpret_node_semver index 379d9233ae..95a9284865 100755 --- a/test/slow/nvm_interpret_node_semver +++ b/test/slow/nvm_interpret_node_semver @@ -29,14 +29,23 @@ while [ -n "$test_cases" ]; do fi done -# TODO add more test cases here -# TODO get the following semvers working: ^7 ^7.x ^7.0 ~8 ~8.0 # Verify actual outputs given some inputs # input:expected_output test_cases="*:$NEWEST_NODE_VERSION +^5:$NEWEST_NODE_VERSION_5 +^5.0:$NEWEST_NODE_VERSION_5 +^5.x:$NEWEST_NODE_VERSION_5 +^5.X:$NEWEST_NODE_VERSION_5 +^5.*:$NEWEST_NODE_VERSION_5 +~5:$NEWEST_NODE_VERSION_5 +~5.0:$NEWEST_NODE_VERSION_5 +~5.x:$NEWEST_NODE_VERSION_5 +~5.X:$NEWEST_NODE_VERSION_5 +~5.*:$NEWEST_NODE_VERSION_5 5:$NEWEST_NODE_VERSION_5 x:$NEWEST_NODE_VERSION X:$NEWEST_NODE_VERSION +x.x.x:$NEWEST_NODE_VERSION 0.12.18:0.12.18 0.11.16:0.11.16 7.0.0||7.2.1:7.2.1