/** * Copyright (C) 2022 Adrien Hopkins * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ package sevenUnits.utils; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static sevenUnits.utils.SemanticVersionNumber.BUILD_METADATA_COMPARATOR; import static sevenUnits.utils.SemanticVersionNumber.builder; import static sevenUnits.utils.SemanticVersionNumber.fromString; import static sevenUnits.utils.SemanticVersionNumber.isValidVersionString; import static sevenUnits.utils.SemanticVersionNumber.preRelease; import static sevenUnits.utils.SemanticVersionNumber.stableVersion; import java.util.List; import org.junit.jupiter.api.Test; /** * Tests for {@link SemanticVersionNumber} * * @since 2022-02-19 */ public final class SemanticVersionTest { /** * Test for {@link SemanticVersionNumber#compatible} * * @since 2022-02-20 */ @Test public void testCompatibility() { assertTrue(stableVersion(1, 0, 0).compatibleWith(stableVersion(1, 0, 5)), "1.0.0 not compatible with 1.0.5"); assertTrue(stableVersion(1, 3, 1).compatibleWith(stableVersion(1, 4, 0)), "1.3.1 not compatible with 1.4.0"); // 0.y.z should not be compatible with any other version assertFalse(stableVersion(0, 4, 0).compatibleWith(stableVersion(0, 4, 1)), "0.4.0 compatible with 0.4.1 (0.y.z versions should be treated as unstable/incompatbile)"); // upgrading major version should = incompatible assertFalse(stableVersion(1, 0, 0).compatibleWith(stableVersion(2, 0, 0)), "1.0.0 compatible with 2.0.0"); // dowgrade should = incompatible assertFalse(stableVersion(1, 1, 0).compatibleWith(stableVersion(1, 0, 0)), "1.1.0 compatible with 1.0.0"); } /** * Tests {@link SemanticVersionNumber#toString} for complex version numbers * * @since 2022-02-19 */ @Test public void testComplexToString() { final SemanticVersionNumber v1 = builder(1, 2, 3).preRelease(1, 2, 3) .build(); assertEquals("1.2.3-1.2.3", v1.toString()); final SemanticVersionNumber v2 = builder(4, 5, 6).preRelease("abc", 123) .buildMetadata("2022-02-19").build(); assertEquals("4.5.6-abc.123+2022-02-19", v2.toString()); final SemanticVersionNumber v3 = builder(1, 0, 0) .preRelease("x-y-z", "--").build(); assertEquals("1.0.0-x-y-z.--", v3.toString()); } /** * Tests that complex version can be created and their parts read * * @since 2022-02-19 */ @Test public void testComplexVersions() { final SemanticVersionNumber v1 = builder(1, 2, 3).preRelease(1, 2, 3) .build(); assertEquals(1, v1.majorVersion()); assertEquals(2, v1.minorVersion()); assertEquals(3, v1.patchVersion()); assertEquals(List.of("1", "2", "3"), v1.preReleaseIdentifiers()); assertEquals(List.of(), v1.buildMetadata()); final SemanticVersionNumber v2 = builder(4, 5, 6).preRelease("abc", 123) .buildMetadata("2022-02-19").build(); assertEquals(4, v2.majorVersion()); assertEquals(5, v2.minorVersion()); assertEquals(6, v2.patchVersion()); assertEquals(List.of("abc", "123"), v2.preReleaseIdentifiers()); assertEquals(List.of("2022-02-19"), v2.buildMetadata()); final SemanticVersionNumber v3 = builder(1, 0, 0) .preRelease("x-y-z", "--").build(); assertEquals(1, v3.majorVersion()); assertEquals(0, v3.minorVersion()); assertEquals(0, v3.patchVersion()); assertEquals(List.of("x-y-z", "--"), v3.preReleaseIdentifiers()); assertEquals(List.of(), v3.buildMetadata()); } /** * Test that semantic version strings can be parsed correctly * * @since 2022-02-19 * @see SemanticVersionNumber#fromString * @see SemanticVersionNumber#isValidVersionString */ @Test public void testFromString() { // test that the regex can match version strings assertTrue(isValidVersionString("1.0.0"), "1.0.0 is treated as invalid"); assertTrue(isValidVersionString("1.3.9"), "1.3.9 is treated as invalid"); assertTrue(isValidVersionString("2.0.0-a.1"), "2.0.0-a.1 is treated as invalid"); assertTrue(isValidVersionString("1.0.0-a.b.c.d"), "1.0.0-a.b.c.d is treated as invalid"); assertTrue(isValidVersionString("1.0.0+abc"), "1.0.0+abc is treated as invalid"); assertTrue(isValidVersionString("1.0.0-abc+def"), "1.0.0-abc+def is treated as invalid"); // test that invalid versions don't match assertFalse(isValidVersionString("1.0"), "1.0 is treated as valid (patch should be required)"); assertFalse(isValidVersionString("1.A.0"), "1.A.0 is treated as valid (main versions must be numbers)"); assertFalse(isValidVersionString("1.0.0-"), "1.0.0- is treated as valid (pre-release must not be empty)"); assertFalse(isValidVersionString("1.0.0+"), "1.0.0+ is treated as valid (build metadata must not be empty)"); // test that versions can be parsed assertEquals(stableVersion(1, 0, 0), fromString("1.0.0"), "Could not parse 1.0.0"); assertEquals( builder(1, 2, 3).preRelease("abc", "56", "def") .buildMetadata("2022abc99").build(), fromString("1.2.3-abc.56.def+2022abc99"), "Could not parse 1.2.3-abc.56.def+2022abc99"); } /** * Ensures it is impossible to create invalid version numbers */ @Test public void testInvalidVersionNumbers() { // stableVersion() assertThrows(IllegalArgumentException.class, () -> stableVersion(1, 0, -1), "Negative patch tolerated by stableVersion"); assertThrows(IllegalArgumentException.class, () -> stableVersion(1, -2, 1), "Negative minor version number tolerated by stableVersion"); assertThrows(IllegalArgumentException.class, () -> stableVersion(-3, 0, 7), "Negative major version number tolerated by stableVersion"); // preRelease() assertThrows(IllegalArgumentException.class, () -> preRelease(1, 0, -1, "test", 2), "Negative patch tolerated by preRelease"); assertThrows(IllegalArgumentException.class, () -> preRelease(1, -2, 1, "test", 2), "Negative minor version number tolerated by preRelease"); assertThrows(IllegalArgumentException.class, () -> preRelease(-3, 0, 7, "test", 2), "Negative major version number tolerated by preRelease"); assertThrows(IllegalArgumentException.class, () -> preRelease(1, 0, 0, "test", -1), "Negative pre release number tolerated by preRelease"); assertThrows(NullPointerException.class, () -> preRelease(1, 0, 0, null, 1), "Null tolerated by preRelease"); assertThrows(IllegalArgumentException.class, () -> preRelease(1, 0, 0, "", 1), "Empty string tolerated by preRelease"); assertThrows(IllegalArgumentException.class, () -> preRelease(1, 0, 0, "abc+cde", 1), "Invalid string tolerated by preRelease"); // builder() assertThrows(IllegalArgumentException.class, () -> builder(1, 0, -1), "Negative patch tolerated by builder"); assertThrows(IllegalArgumentException.class, () -> builder(1, -2, 1), "Negative minor version number tolerated by builder"); assertThrows(IllegalArgumentException.class, () -> builder(-3, 0, 7), "Negative major version number tolerated by builder"); final SemanticVersionNumber.Builder testBuilder = builder(1, 2, 3); // note: builder.buildMetadata(null) doesn't even compile lol // builder.buildMetadata assertThrows(NullPointerException.class, () -> testBuilder.buildMetadata(null, "abc"), "Null tolerated by builder.buildMetadata(String...)"); assertThrows(IllegalArgumentException.class, () -> testBuilder.buildMetadata(""), "Empty string tolerated by builder.buildMetadata(String...)"); assertThrows(IllegalArgumentException.class, () -> testBuilder.buildMetadata("c%4"), "Invalid string tolerated by builder.buildMetadata(String...)"); assertThrows(NullPointerException.class, () -> testBuilder.buildMetadata(List.of("abc", null)), "Null tolerated by builder.buildMetadata(List)"); assertThrows(IllegalArgumentException.class, () -> testBuilder.buildMetadata(List.of("")), "Empty string tolerated by builder.buildMetadata(List)"); assertThrows(IllegalArgumentException.class, () -> testBuilder.buildMetadata(List.of("")), "Invalid string tolerated by builder.buildMetadata(List)"); // builder.preRelease assertThrows(NullPointerException.class, () -> testBuilder.preRelease(null, "abc"), "Null tolerated by builder.preRelease(String...)"); assertThrows(IllegalArgumentException.class, () -> testBuilder.preRelease(""), "Empty string tolerated by builder.preRelease(String...)"); assertThrows(IllegalArgumentException.class, () -> testBuilder.preRelease("c%4"), "Invalid string tolerated by builder.preRelease(String...)"); assertThrows(NullPointerException.class, () -> testBuilder.preRelease(List.of("abc", null)), "Null tolerated by builder.preRelease(List)"); assertThrows(IllegalArgumentException.class, () -> testBuilder.preRelease(List.of("")), "Empty string tolerated by builder.preRelease(List)"); assertThrows(IllegalArgumentException.class, () -> testBuilder.preRelease(List.of("")), "Invalid string tolerated by builder.preRelease(List)"); // the overloadings that accept numeric arguments assertThrows(IllegalArgumentException.class, () -> testBuilder.preRelease(-1), "Negative number tolerated by builder.preRelease(int...)"); assertThrows(IllegalArgumentException.class, () -> testBuilder.preRelease("abc", -1), "Negative number tolerated by builder.preRelease(String, int)"); assertThrows(NullPointerException.class, () -> testBuilder.preRelease(null, 1), "Null tolerated by builder.preRelease(String, int)"); assertThrows(IllegalArgumentException.class, () -> testBuilder.preRelease("", 1), "Empty string tolerated by builder.preRelease(String, int)"); assertThrows(IllegalArgumentException.class, () -> testBuilder.preRelease("#$#c", 1), "Invalid string tolerated by builder.preRelease(String, int)"); // ensure all these attempts didn't change the builder assertEquals(builder(1, 2, 3), testBuilder, "Attempts at making invalid version number succeeded despite throwing errors"); } /** * Test for {@link SemanticVersionNumber#isStable} * * @since 2022-02-19 */ @Test public void testIsStable() { assertTrue(stableVersion(1, 0, 0).isStable(), "1.0.0 should be stable but is not"); assertFalse(stableVersion(0, 1, 2).isStable(), "0.1.2 should not be stable but is"); assertFalse(preRelease(1, 2, 3, "alpha", 5).isStable(), "1.2.3a5 should not be stable but is"); assertTrue( builder(9, 9, 99) .buildMetadata("lots-of-metadata", "abc123", "2022").build() .isStable(), "9.9.99+lots-of-metadata.abc123.2022 should be stable but is not"); } /** * Tests that the versions are ordered by * {@link SemanticVersionNumber#compareTo} according to official rules. Tests * all of the versions compared in section 11 of the SemVer 2.0.0 document * and some more. * * @since 2022-02-19 */ @Test public void testOrder() { final SemanticVersionNumber v100a = builder(1, 0, 0).preRelease("alpha") .build(); // 1.0.0-alpha final SemanticVersionNumber v100a1 = preRelease(1, 0, 0, "alpha", 1); // 1.0.0-alpha.1 final SemanticVersionNumber v100ab = builder(1, 0, 0) .preRelease("alpha", "beta").build(); // 1.0.0-alpha.beta final SemanticVersionNumber v100b = builder(1, 0, 0).preRelease("beta") .build(); // 1.0.0-alpha final SemanticVersionNumber v100b2 = preRelease(1, 0, 0, "beta", 2); // 1.0.0-beta.2 final SemanticVersionNumber v100b11 = preRelease(1, 0, 0, "beta", 11); // 1.0.0-beta.11 final SemanticVersionNumber v100rc1 = preRelease(1, 0, 0, "rc", 1); // 1.0.0-rc.1 final SemanticVersionNumber v100 = stableVersion(1, 0, 0); final SemanticVersionNumber v100plus = builder(1, 0, 0) .buildMetadata("blah", "blah", "blah").build(); // 1.0.0+blah.blah.blah final SemanticVersionNumber v200 = stableVersion(2, 0, 0); final SemanticVersionNumber v201 = stableVersion(2, 0, 1); final SemanticVersionNumber v210 = stableVersion(2, 1, 0); final SemanticVersionNumber v211 = stableVersion(2, 1, 1); final SemanticVersionNumber v300 = stableVersion(3, 0, 0); // test order of version numbers assertTrue(v100a.compareTo(v100a1) < 0, "1.0.0-alpha >= 1.0.0-alpha.1"); assertTrue(v100a1.compareTo(v100ab) < 0, "1.0.0-alpha.1 >= 1.0.0-alpha.beta"); assertTrue(v100ab.compareTo(v100b) < 0, "1.0.0-alpha.beta >= 1.0.0-beta"); assertTrue(v100b.compareTo(v100b2) < 0, "1.0.0-beta >= 1.0.0-beta.2"); assertTrue(v100b2.compareTo(v100b11) < 0, "1.0.0-beta.2 >= 1.0.0-beta.11"); assertTrue(v100b11.compareTo(v100rc1) < 0, "1.0.0-beta.11 >= 1.0.0-rc.1"); assertTrue(v100rc1.compareTo(v100) < 0, "1.0.0-rc.1 >= 1.0.0"); assertTrue(v100.compareTo(v200) < 0, "1.0.0 >= 2.0.0"); assertTrue(v200.compareTo(v201) < 0, "2.0.0 >= 2.0.1"); assertTrue(v201.compareTo(v210) < 0, "2.0.1 >= 2.1.0"); assertTrue(v210.compareTo(v211) < 0, "2.1.0 >= 2.1.1"); assertTrue(v211.compareTo(v300) < 0, "2.1.1 >= 3.0.0"); // test symmetry - assume previous tests passed assertTrue(v100a1.compareTo(v100a) > 0, "1.0.0-alpha.1 <= 1.0.0-alpha"); assertTrue(v100.compareTo(v100rc1) > 0, "1.0.0 <= 1.0.0-rc.1"); assertTrue(v300.compareTo(v211) > 0, "3.0.0 <= 2.1.1"); // test transitivity assertTrue(v100a.compareTo(v100b11) < 0, "1.0.0-alpha >= 1.0.0-beta.11"); assertTrue(v100b.compareTo(v200) < 0, "1.0.0-beta >= 2.0.0"); assertTrue(v100.compareTo(v300) < 0, "1.0.0 >= 3.0.0"); assertTrue(v100a.compareTo(v300) < 0, "1.0.0-alpha >= 3.0.0"); // test metadata is ignored assertEquals(0, v100.compareTo(v100plus), "Build metadata not ignored"); // test metadata is NOT ignored by alternative comparator assertTrue(BUILD_METADATA_COMPARATOR.compare(v100, v100plus) > 0, "Build metadata ignored by BUILD_METADATA_COMPARATOR"); } /** * Tests that simple stable versions can be created and their parts read * * @since 2022-02-19 */ @Test public void testSimpleStableVersions() { final SemanticVersionNumber v100 = stableVersion(1, 0, 0); assertEquals(1, v100.majorVersion()); assertEquals(0, v100.minorVersion()); assertEquals(0, v100.patchVersion()); final SemanticVersionNumber v925 = stableVersion(9, 2, 5); assertEquals(9, v925.majorVersion()); assertEquals(2, v925.minorVersion()); assertEquals(5, v925.patchVersion()); } /** * Tests that {@link SemanticVersionNumber#toString} works for simple version * numbers * * @since 2022-02-19 */ @Test public void testSimpleToString() { final SemanticVersionNumber v100 = stableVersion(1, 0, 0); assertEquals("1.0.0", v100.toString()); final SemanticVersionNumber v845a1 = preRelease(8, 4, 5, "alpha", 1); assertEquals("8.4.5-alpha.1", v845a1.toString()); } /** * Tests that simple unstable versions can be created and their parts read * * @since 2022-02-19 */ @Test public void testSimpleUnstableVersions() { final SemanticVersionNumber v350a1 = preRelease(3, 5, 0, "alpha", 1); assertEquals(3, v350a1.majorVersion(), "Incorrect major version for v3.5.0a1"); assertEquals(5, v350a1.minorVersion(), "Incorrect minor version for v3.5.0a1"); assertEquals(0, v350a1.patchVersion(), "Incorrect patch version for v3.5.0a1"); assertEquals(List.of("alpha", "1"), v350a1.preReleaseIdentifiers(), "Incorrect pre-release identifiers for v3.5.0a1"); } }