You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jena.apache.org by an...@apache.org on 2021/02/22 20:50:05 UTC

[jena] branch main updated: JENA-2031: More tests for IRIx

This is an automated email from the ASF dual-hosted git repository.

andy pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/jena.git


The following commit(s) were added to refs/heads/main by this push:
     new 5b918b4  JENA-2031: More tests for IRIx
     new ccd3904  Merge pull request #931 from afs/irix-tests
5b918b4 is described below

commit 5b918b4d87a99ab11d4eb3039d0c23df83339cf6
Author: Andy Seaborne <an...@apache.org>
AuthorDate: Mon Feb 22 13:09:00 2021 +0000

    JENA-2031: More tests for IRIx
---
 .../java/org/apache/jena/irix/IRIProviderJDK.java  |  18 ++
 .../org/apache/jena/irix/IRIProviderJenaIRI.java   |  83 +++++--
 .../src/main/java/org/apache/jena/irix/IRIx.java   |   7 +
 .../org/apache/jena/irix/AbstractTestIRIx.java     |  83 +++++++
 .../test/java/org/apache/jena/irix/JenaIRI.java    |  79 ++++++
 .../test/java/org/apache/jena/irix/TS_IRIx.java    |  43 ++++
 .../test/java/org/apache/jena/irix/TestIRIx.java   |  72 +-----
 .../java/org/apache/jena/irix/TestNormalize.java   | 120 +++++++++
 .../java/org/apache/jena/irix/TestRFC3986.java     | 272 +++++++++++++++++++++
 .../java/org/apache/jena/irix/TestRelative.java    |  79 ++++++
 .../java/org/apache/jena/irix/TestResolve.java     | 206 ++++++++++++++++
 .../java/org/apache/jena/test/TestPackage.java     |   2 +-
 .../java/org/apache/jena/iri/ViolationCodes.java   |   2 +-
 13 files changed, 976 insertions(+), 90 deletions(-)

diff --git a/jena-core/src/main/java/org/apache/jena/irix/IRIProviderJDK.java b/jena-core/src/main/java/org/apache/jena/irix/IRIProviderJDK.java
index 2013409..48ab041 100644
--- a/jena-core/src/main/java/org/apache/jena/irix/IRIProviderJDK.java
+++ b/jena-core/src/main/java/org/apache/jena/irix/IRIProviderJDK.java
@@ -20,6 +20,7 @@ package org.apache.jena.irix;
 
 import java.net.URI;
 import java.net.URISyntaxException;
+import java.util.Objects;
 import java.util.function.Supplier;
 
 /**
@@ -118,6 +119,23 @@ public class IRIProviderJDK implements IRIProvider {
                 return new IRIxJDK(iri2.toString(), iri2);
             });
         }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(javaURI);
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if ( this == obj )
+                return true;
+            if ( obj == null )
+                return false;
+            if ( getClass() != obj.getClass() )
+                return false;
+            IRIxJDK other = (IRIxJDK)obj;
+            return Objects.equals(javaURI, other.javaURI);
+        }
     }
 
     @Override
diff --git a/jena-core/src/main/java/org/apache/jena/irix/IRIProviderJenaIRI.java b/jena-core/src/main/java/org/apache/jena/irix/IRIProviderJenaIRI.java
index 060706d..78debe5 100644
--- a/jena-core/src/main/java/org/apache/jena/irix/IRIProviderJenaIRI.java
+++ b/jena-core/src/main/java/org/apache/jena/irix/IRIProviderJenaIRI.java
@@ -20,6 +20,8 @@ package org.apache.jena.irix;
 
 import java.io.PrintStream;
 import java.util.Iterator;
+import java.util.Objects;
+import java.util.regex.Pattern;
 
 import org.apache.jena.iri.*;
 import org.apache.jena.iri.impl.PatternCompiler;
@@ -96,6 +98,9 @@ public class IRIProviderJenaIRI implements IRIProvider {
         static private int relFlags = IRIRelativize.SAMEDOCUMENT | IRIRelativize.CHILD ;
         @Override
         public IRIx relativize(IRIx other) {
+            // Align of IRI3986 algorithm.
+            if (jenaIRI.getRawQuery() != null )
+                return null;
             IRIxJena iriOther = (IRIxJena)other;
             IRI iri2 = jenaIRI.relativize(iriOther.jenaIRI, relFlags);
             if ( iri2.equals(iriOther.jenaIRI))
@@ -103,6 +108,23 @@ public class IRIProviderJenaIRI implements IRIProvider {
             IRIProviderJenaIRI.exceptions(iri2);
             return new IRIxJena(iri2.toString(), iri2);
         }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(jenaIRI);
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if ( this == obj )
+                return true;
+            if ( obj == null )
+                return false;
+            if ( getClass() != obj.getClass() )
+                return false;
+            IRIxJena other = (IRIxJena)obj;
+            return Objects.equals(jenaIRI, other.jenaIRI);
+        }
     }
 
     @Override
@@ -110,6 +132,12 @@ public class IRIProviderJenaIRI implements IRIProvider {
         // "create" - does not throw exceptions
         IRI iriObj = iriFactory().create(iriStr);
         // errors and warnings.
+        if ( STRICT_FILE && isFILE(iriObj) ) {
+            if ( iriStr.startsWith("file://" ) && ! iriStr.startsWith("file:///") )
+                throw new IRIException("file: URLs should start file:///");
+        }
+        if ( isUUID(iriObj) )
+            checkUUID(iriObj, iriStr);
         exceptions(iriObj);
         return new IRIProviderJenaIRI.IRIxJena(iriStr, iriObj);
     }
@@ -161,21 +189,8 @@ public class IRIProviderJenaIRI implements IRIProvider {
             Violation v = vIter.next();
             int code = v.getViolationCode() ;
             // Filter codes.
-            if ( code == Violation.PERCENT_ENCODING_SHOULD_BE_UPPERCASE)
-                continue;
-
-            if ( code == Violation.SCHEME_PATTERN_MATCH_FAILED && isURN(iri) && ! STRICT_URN )
-                continue;
-
-            if ( code == Violation.REQUIRED_COMPONENT_MISSING && isFILE(iri) )
-                // jena-iri implements the earlier RFCs, not RFC8089 which adds "file:local"
-                continue;
-            //break to retain errors
+            // Global settings below; this section is for conditional filtering.
             switch(code) {
-                //case Violation.LOWERCASE_PREFERRED:
-                case Violation.PERCENT_ENCODING_SHOULD_BE_UPPERCASE:
-                    continue;
-
                 case Violation.SCHEME_PATTERN_MATCH_FAILED:
                     if ( isURN(iri) && ! STRICT_URN )
                         continue;
@@ -183,11 +198,12 @@ public class IRIProviderJenaIRI implements IRIProvider {
                         continue;
                     break;
                 case Violation.REQUIRED_COMPONENT_MISSING:
-                    if ( isFILE(iri) && ! STRICT_FILE )
+                    // jena-iri handling of file: URIs is only for (an interpretation of) RFC 1738.
+                    // RFC8089 allows relative file URIs and a wider use of characters.
+                    if ( isFILE(iri) )
                         continue;
-                default:
             }
-            String msg = iri.violations(false).next().getShortMessage();
+            String msg = v.getShortMessage();
             throw new IRIException(msg);
         }
         return iri;
@@ -196,7 +212,27 @@ public class IRIProviderJenaIRI implements IRIProvider {
     private static boolean isURN(IRI iri)  { return "urn".equalsIgnoreCase(iri.getScheme()); }
     private static boolean isFILE(IRI iri) { return "file".equalsIgnoreCase(iri.getScheme()); }
 
-    private static final boolean   ShowResolverSetup = false;
+    // Checks trailing part of URI.
+    // Works on "urn:" and "urn:uuid:".
+    private static Pattern UUID_PATTERN = Pattern.compile(":[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$");
+
+    private boolean isUUID(IRI iri) {
+        if ( "uuid".equalsIgnoreCase(iri.getScheme()) )
+                return true;
+        return iri.getRawPath().startsWith("uuid:");
+    }
+
+    private void checkUUID(IRI iriObj, String original) {
+        if ( iriObj.getRawFragment() != null )
+            throw new IRIException("Fragment used with UUID");
+        if ( iriObj.getRawQuery() != null )
+            throw new IRIException("Query used with UUID");
+        boolean matches = UUID_PATTERN.matcher(original).matches();
+        if ( !matches )
+            throw new IRIException("Not a valid UUID string: "+original);
+    }
+
+    private static final boolean ShowResolverSetup = false;
 
     private static final IRIFactory iriFactoryInst = new IRIFactory();
     static {
@@ -216,14 +252,17 @@ public class IRIProviderJenaIRI implements IRIProvider {
 
         // Accept any scheme.
         setErrorWarning(iriFactoryInst, ViolationCodes.UNREGISTERED_IANA_SCHEME, false, false);
-        setErrorWarning(iriFactoryInst, ViolationCodes.NON_INITIAL_DOT_SEGMENT, false, false);
+        setErrorWarning(iriFactoryInst, ViolationCodes.UNREGISTERED_NONIETF_SCHEME_TREE, false, false);
 
+        setErrorWarning(iriFactoryInst, ViolationCodes.NON_INITIAL_DOT_SEGMENT, false, false);
         setErrorWarning(iriFactoryInst, ViolationCodes.LOWERCASE_PREFERRED, false, true);
         setErrorWarning(iriFactoryInst, ViolationCodes.REQUIRED_COMPONENT_MISSING, true, true);
 
-        // Choices: setting here does not seem to have any effect. See CheckerIRI and IRIProviderJena for filtering.
-//      //setErrorWarning(iriFactoryInst, ViolationCodes.PERCENT_ENCODING_SHOULD_BE_UPPERCASE, false, true);
-//      //setErrorWarning(iriFactoryInst, ViolationCodes.SCHEME_PATTERN_MATCH_FAILED, false, true);
+        setErrorWarning(iriFactoryInst, ViolationCodes.PERCENT_ENCODING_SHOULD_BE_UPPERCASE, false, false);
+
+        // jena-iri has not been updated for percent in DNS name (RFC 3986)
+        setErrorWarning(iriFactoryInst, ViolationCodes.NOT_DNS_NAME, false, false);
+        setErrorWarning(iriFactoryInst, ViolationCodes.USE_PUNYCODE_NOT_PERCENTS, false, false);
 
         // NFC tests are not well understood by general developers and these cause confusion.
         // See JENA-864
diff --git a/jena-core/src/main/java/org/apache/jena/irix/IRIx.java b/jena-core/src/main/java/org/apache/jena/irix/IRIx.java
index ccae1b2..aa37a20 100644
--- a/jena-core/src/main/java/org/apache/jena/irix/IRIx.java
+++ b/jena-core/src/main/java/org/apache/jena/irix/IRIx.java
@@ -186,6 +186,13 @@ public abstract class IRIx {
         return iriString;
     }
 
+    // Provide value-based equality.
+    @Override
+    public abstract int hashCode();
+
+    @Override
+    public abstract boolean equals(Object other);
+
     /**
      * User readable form. Not guaranteed to be usable as a string
      * in other API calls.
diff --git a/jena-core/src/test/java/org/apache/jena/irix/AbstractTestIRIx.java b/jena-core/src/test/java/org/apache/jena/irix/AbstractTestIRIx.java
new file mode 100644
index 0000000..b4135a0
--- /dev/null
+++ b/jena-core/src/test/java/org/apache/jena/irix/AbstractTestIRIx.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.irix;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.runners.Parameterized.Parameters;
+
+/** Test suite driver for IRIx */
+public class AbstractTestIRIx {
+
+    @Parameters(name = "{index}: {0}")
+    public static Iterable<Object[]> data() {
+        SystemIRIx.init();
+        List<Object[]> data = new ArrayList<>();
+        //data.add(new Object[]{"IRI3986", new IRIProvider3986()});
+        data.add(new Object[]{"JenaIRI", new IRIProviderJenaIRI()});
+        // Does not pass the test suite.
+        //data.add(new Object[]{"JDK.URI", new IRIProviderJDK()});
+        return data;
+    }
+
+    protected static void setProvider(IRIProvider provider) {
+        provider.strictMode("urn", true);
+        provider.strictMode("file", true);
+        SystemIRIx.setProvider(provider);
+    }
+
+    protected static IRIProvider getProvider() {
+        return SystemIRIx.getProvider();
+    }
+
+    protected void notStrict(String scheme, Runnable action) {
+        provider.strictMode(scheme, false);
+        try { action.run(); }
+        finally { provider.strictMode(scheme, true); }
+    }
+
+    private final IRIProvider provider;
+    private static IRIProvider systemProvider;
+
+    @BeforeClass static public void beforeClass() {
+        systemProvider = getProvider();
+    }
+
+    @AfterClass static public void afterClass() {
+        setProvider(systemProvider);
+    }
+
+    @Before public void beforeTest() {
+        systemProvider = getProvider();
+        setProvider(provider);
+    }
+
+    @After public void afterTest() {
+        setProvider(systemProvider);
+    }
+
+    protected AbstractTestIRIx(String name, IRIProvider provider) {
+        this.provider = provider;
+    }
+}
diff --git a/jena-core/src/test/java/org/apache/jena/irix/JenaIRI.java b/jena-core/src/test/java/org/apache/jena/irix/JenaIRI.java
new file mode 100644
index 0000000..d15dd29
--- /dev/null
+++ b/jena-core/src/test/java/org/apache/jena/irix/JenaIRI.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.irix;
+
+import org.apache.jena.iri.IRIFactory;
+import org.apache.jena.iri.ViolationCodes;
+
+// The Jena IRI settings using in ARQ/RIOT.
+class JenaIRI {
+
+    public static IRIFactory iriFactory() {
+        return iriFactoryInst;
+    }
+
+    private static final IRIFactory iriFactoryInst = new IRIFactory();
+    static {
+        iriFactoryInst.useSpecificationIRI(true);
+        iriFactoryInst.useSchemeSpecificRules("*", true);
+
+        // Allow relative references for file: URLs.
+        iriFactoryInst.setSameSchemeRelativeReferences("file");
+
+        // Convert "SHOULD" to warning (default is "error").
+        // iriFactory.shouldViolation(false,true);
+
+        // Accept any scheme.
+        setErrorWarning(iriFactoryInst, ViolationCodes.UNREGISTERED_IANA_SCHEME, false, false);
+        setErrorWarning(iriFactoryInst, ViolationCodes.UNREGISTERED_NONIETF_SCHEME_TREE, false, false);
+
+        setErrorWarning(iriFactoryInst, ViolationCodes.NON_INITIAL_DOT_SEGMENT, false, false);
+        setErrorWarning(iriFactoryInst, ViolationCodes.LOWERCASE_PREFERRED, false, true);
+        setErrorWarning(iriFactoryInst, ViolationCodes.REQUIRED_COMPONENT_MISSING, true, true);
+
+        setErrorWarning(iriFactoryInst, ViolationCodes.PERCENT_ENCODING_SHOULD_BE_UPPERCASE, false, false);
+
+        // jena-iri has not been updated for percent in DNS name (RFC 3986)
+        setErrorWarning(iriFactoryInst, ViolationCodes.NOT_DNS_NAME, false, false);
+        setErrorWarning(iriFactoryInst, ViolationCodes.USE_PUNYCODE_NOT_PERCENTS, false, false);
+
+        // NFC tests are not well understood by general developers and these cause confusion.
+        // See JENA-864
+        // NFC is in RDF 1.1 so do test for that.
+        // https://www.w3.org/TR/rdf11-concepts/#section-IRIs
+        // Leave switched on as a warning.
+        //setErrorWarning(iriFactoryInst, ViolationCodes.NOT_NFC,  false, false);
+
+        // NFKC is not mentioned in RDF 1.1. Switch off.
+        setErrorWarning(iriFactoryInst, ViolationCodes.NOT_NFKC, false, false);
+
+        // ** Applies to various unicode blocks.
+        // The set of legal characters depends on the Java version.
+        // If not set, this causes test failures in Turtle and Trig eval tests.
+        // "Any" unicode codepoint.
+        setErrorWarning(iriFactoryInst, ViolationCodes.COMPATIBILITY_CHARACTER, false, false);
+        setErrorWarning(iriFactoryInst, ViolationCodes.UNDEFINED_UNICODE_CHARACTER, false, false);
+        setErrorWarning(iriFactoryInst, ViolationCodes.UNASSIGNED_UNICODE_CHARACTER, false, false);
+    }
+
+    private static void setErrorWarning(IRIFactory factory, int code, boolean isError, boolean isWarning) {
+        factory.setIsError(code, isError);
+        factory.setIsWarning(code, isWarning);
+    }
+}
diff --git a/jena-core/src/test/java/org/apache/jena/irix/TS_IRIx.java b/jena-core/src/test/java/org/apache/jena/irix/TS_IRIx.java
new file mode 100644
index 0000000..affa067
--- /dev/null
+++ b/jena-core/src/test/java/org/apache/jena/irix/TS_IRIx.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.irix;
+
+import junit.framework.JUnit4TestAdapter;
+import junit.framework.TestSuite;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+@RunWith(Suite.class)
+@Suite.SuiteClasses( {
+    // IRIx tests with matrix of providers.
+    TestIRIx.class,
+    TestRFC3986.class,
+    TestResolve.class,
+    TestNormalize.class,
+    TestRelative.class,
+} )
+
+public class TS_IRIx {
+    public static TestSuite suite() {
+        TestSuite ts = new TestSuite();
+        ts.setName("IRIx");
+        ts.addTest(new JUnit4TestAdapter(TS_IRIx.class));
+        return ts;
+    }
+}
diff --git a/jena-core/src/test/java/org/apache/jena/irix/TestIRIx.java b/jena-core/src/test/java/org/apache/jena/irix/TestIRIx.java
index 347cba7..fae9c44 100644
--- a/jena-core/src/test/java/org/apache/jena/irix/TestIRIx.java
+++ b/jena-core/src/test/java/org/apache/jena/irix/TestIRIx.java
@@ -18,74 +18,18 @@
 
 package org.apache.jena.irix;
 
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
-import java.util.ArrayList;
-import java.util.List;
-
-import junit.framework.JUnit4TestAdapter;
-import junit.framework.TestSuite;
-import org.junit.*;
+import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class TestIRIx {
-
-    public static TestSuite suite() {
-        TestSuite ts = new TestSuite();
-        ts.setName("IRIx");
-        ts.addTest(new JUnit4TestAdapter(TestIRIx.class));
-        return ts;
-    }
-
-    // These are not complete tests for IRIs.
-    // They cover corner cases and the expected functionality of IRI
-    // providers and assume that RFC3986 parsing is (mostly) correct.
-    // (by taking the tests from iri4ld they could be made complete).
-
-    @Parameters(name = "{index}: {0}")
-    public static Iterable<Object[]> data() {
-        List<Object[]> data = new ArrayList<>();
-        data.add(new Object[]{"JenaIRI", new IRIProviderJenaIRI()});
-        // Future
-        // data.add(new Object[]{"IRI3986", new IRIProvider3986()});
-        // For completeness: see javadoc for reasons not to use this.
-        //  data.add(new Object[]{"JDK.URI", new IRIProviderJDK()});
-        return data;
-    }
-
-    private void notStrict(String scheme, Runnable action) {
-        provider.strictMode(scheme, false);
-        try { action.run(); }
-        finally { provider.strictMode(scheme, true); }
-    }
-
-    private final IRIProvider provider;
-    private static IRIProvider systemProvider;
-
-    @BeforeClass static public void beforeClass() {
-        systemProvider = SystemIRIx.getProvider();
-    }
-
-    @AfterClass static public void afterClass() {
-        SystemIRIx.setProvider(systemProvider);
-    }
-
-    @Before public void beforeTest() {
-        systemProvider = SystemIRIx.getProvider();
-        provider.strictMode("urn", true);
-        provider.strictMode("file", true);
-        SystemIRIx.setProvider(provider);
-    }
-
-    @After public void afterTest() {
-        SystemIRIx.setProvider(systemProvider);
-    }
+public class TestIRIx extends AbstractTestIRIx {
 
     public TestIRIx(String name, IRIProvider provider) {
-        this.provider = provider;
+        super(name, provider);
     }
 
     // ---- RFC 3986 Grammar
@@ -124,7 +68,7 @@ public class TestIRIx {
     // [] not in IPv6 address
     public void http_07()   { parse("http://h/ab[]"); }
 
-    public void http_08()   { parse("http://example/~jena/file"); }
+    @Test public void http_08() { parse("http://example/~jena/file"); }
 
     // -- Compliance with URN scheme: https://tools.ietf.org/html/rfc8141
 
@@ -226,10 +170,6 @@ public class TestIRIx {
 
     @Test public void misc_01()     { reference("wm:/abc", true); }
 
-    @Test(expected=IRIException.class)
-    // Bad hex code: Breaks RFC 3986 grammar.
-    public void misc_02()           { parse("http://h/ab%XXcd"); }
-
     private void relative(String baseUriStr, String otherStr, String expected) {
         IRIx base = IRIx.create(baseUriStr);
         IRIx relInput = IRIx.create(otherStr);
diff --git a/jena-core/src/test/java/org/apache/jena/irix/TestNormalize.java b/jena-core/src/test/java/org/apache/jena/irix/TestNormalize.java
new file mode 100644
index 0000000..212f0a9
--- /dev/null
+++ b/jena-core/src/test/java/org/apache/jena/irix/TestNormalize.java
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.irix;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeFalse;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class TestNormalize extends AbstractTestIRIx {
+
+    public TestNormalize(String name, IRIProvider provider) {
+        super(name, provider);
+    }
+
+    @Test
+    public void normalize_01() {
+        testNormalize("http://host/a/b/c?q=1#2", "http://host/a/b/c?q=1#2");
+    }
+
+    @Test
+    public void normalize_02() {
+        testNormalize("HtTp://host/a/b/c?q=1#2", "http://host/a/b/c?q=1#2");
+    }
+
+    @Test
+    public void normalize_03() {
+        testNormalize("HTTP://HOST/a/b/c?q=1#2", "http://host/a/b/c?q=1#2");
+    }
+
+    @Test
+    public void normalize_04() {
+        testNormalize("HTTP://HOST:/a/b/c?q=1#2", "http://host/a/b/c?q=1#2");
+    }
+
+    @Test
+    public void normalize_05() {
+        testNormalize("HTTP://HOST:80/a/b/c?q=1#2", "http://host/a/b/c?q=1#2");
+    }
+
+    @Test
+    public void normalize_06() {
+        testNormalize("HTTPs://HOST:443/a/b/c?q=1#2", "https://host/a/b/c?q=1#2");
+    }
+
+    @Test
+    public void normalize_07() {
+        testNormalize("http://host", "http://host/");
+    }
+
+    @Test
+    public void normalize_08() {
+        testNormalize("http://host#frag", "http://host/#frag");
+    }
+
+    @Test
+    public void normalize_09() {
+        testNormalize("http://host?q=s", "http://host/?q=s");
+    }
+
+    @Test
+    public void normalize_10() {
+        testNormalize("http://host/?q=s", "http://host/?q=s");
+    }
+
+    @Test
+    public void normalize_11() {
+        testNormalize("http://host%20/?q=s", "http://host%20/?q=s");
+    }
+
+    @Test
+    public void normalize_12() {
+        testNormalize("http://hOSt%20/?q=s", "http://host%20/?q=s");
+    }
+
+    @Test
+    public void normalize_13() {
+        testNormalize("http://hOSt%20/foo%62ar?q=s", "http://host%20/foobar?q=s");
+    }
+
+    @Test
+    public void normalize_14() {
+        testNormalize("http://host/foobar?q=s%74", "http://host/foobar?q=st");
+    }
+
+    @Test
+    public void normalize_15() {
+        testNormalize("http://host/foobar#%7E", "http://host/foobar#~");
+    }
+
+    private void testNormalize(String input, String expected) {
+        // jena-iri does not implement normalization.
+        assumeFalse("jena-iri does not implement normalization", SystemIRIx.getProvider() instanceof IRIProviderJenaIRI );
+//        if ( SystemIRIx.getProvider() instanceof IRIProviderJenaIRI )
+//            return;
+        IRIx iri = IRIx.create(input);
+        IRIx iri2 = iri.normalize();
+        String s = iri2.toString();
+        assertEquals(expected, s);
+    }
+}
diff --git a/jena-core/src/test/java/org/apache/jena/irix/TestRFC3986.java b/jena-core/src/test/java/org/apache/jena/irix/TestRFC3986.java
new file mode 100644
index 0000000..14c9c2c
--- /dev/null
+++ b/jena-core/src/test/java/org/apache/jena/irix/TestRFC3986.java
@@ -0,0 +1,272 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.irix;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import org.apache.jena.iri.IRI;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
+import org.junit.runners.Parameterized;
+
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@RunWith(Parameterized.class)
+public class TestRFC3986 extends AbstractTestIRIx {
+    public TestRFC3986(String name, IRIProvider provider) {
+        super(name, provider);
+    }
+
+    // Assumes full authority parsing and not scheme-specific checks.
+
+    @Test public void parse_00() { good("http://host"); }
+
+    @Test public void parse_01() { good("http://host:8081/abc/def?qs=ghi#jkl"); }
+
+    @Test public void parse_02() { good("http://[::1]:8080/abc/def?qs=ghi#jkl"); }
+
+    // jena-iri does not allow %XX in the authority. It originated pre-3986 and does not seem to have updated completely.
+    // %XX in host added at RFC 3986.
+    @Test public void parse_03() { goodNoIRICheck("http://ab%AAdef/xyzβ/abc"); }
+
+    @Test public void parse_04() { good("/abcdef"); }
+
+    @Test public void parse_05() { good("/ab%FFdef"); }
+
+    // jena-iri mandates upper case.  The RFC 3986 grammar only has upper case but the normalization section discusses lower case.
+    // Unclear status.
+    @Test public void parse_06() { goodNoIRICheck("/ab%ffdef"); }
+
+    @Test public void parse_07() { good("http://host/abcdef?qs=foo#frag"); }
+
+    @Test public void parse_08() { good(""); }
+
+    @Test public void parse_09() { good("."); }
+
+    @Test public void parse_10() { good(".."); }
+
+    @Test public void parse_11() { good("//host:8081/abc/def?qs=ghi#jkl"); }
+
+    @Test public void parse_12() { goodNoIRICheck("a+.-9://h/"); }
+
+    // No path.
+
+    @Test public void parse_13() { good("http://host"); }
+
+    @Test public void parse_14() { good("http://host#frag"); }
+
+    @Test public void parse_15() { good("http://host?query"); }
+
+    // : in first segment in path.
+    @Test public void parse_16() { good("http://host/a:b/"); }
+
+    @Test public void parse_17() { good("/a:b/"); }
+
+    @Test public void parse_18() { good("/z/a:b"); }
+
+    @Test public void equality_01() {
+        String s = "https://jena.apache.org/";
+        IRIx iri1 = IRIx.create(s);
+        IRIx iri2 = IRIx.create(s);
+        assertEquals(iri1, iri2);
+        assertEquals(iri1.hashCode(), iri2.hashCode());
+    }
+
+    // HTTP scheme specific rules.
+    @Test public void parse_http_01()   { badSpecific("http:///file/name.txt"); }
+
+    // HTTP scheme specific rules.
+    @Test public void parse_http_02()   { badSpecific("HTTP:///file/name.txt"); }
+
+    // This is treated as legal with path and no authority.
+    //@Test public void parse_http_02a()   { badSpecific("http:/file/name.txt"); }
+
+    @Test public void parse_http_03()   { badSpecific("http://users@host/file/name.txt"); }
+
+    @Test public void parse_http_04()   { good("nothttp://users@host/file/name.txt"); }
+
+    @Test public void parse_http_05()   { good("nothttp://users@/file/name.txt"); }
+
+    @Test public void parse_file_01() { good("file:///file/name.txt"); }
+
+    // We reject "file://host/" forms.
+    @Test public void parse_file_02() { badSpecific("file://host/file/name.txt"); }
+
+    // This is legal by RFC 8089 (jena-iri, based on the original RFC 1738, fails this with missing authority).
+    @Test public void parse_file_03() { goodNoIRICheck("file:/file/name.txt"); }
+
+    @Test public void parse_urn_01() { good("urn:x-local:abc/def"); }
+
+    // rq-components = [ "?+" r-component ]
+    //                 [ "?=" q-component ]
+
+    @Test public void parse_urn_02()        { good("urn:x-local:abc/def?+more"); }
+
+    @Test public void parse_urn_03()        { good("urn:x-local:abc/def?=123"); }
+
+    @Test public void parse_urn_04()        { good("urn:x-local:abc/def?+resolve?=123#frag"); }
+
+    @Test public void parse_urn_05()        { good("urn:abc0:def"); }
+
+    @Test public void parse_ftp_01() { good("ftp://user@host:3333/abc/def?qs=ghi#jkl"); }
+
+    @Test public void parse_ftp_02() { good("ftp://[::1]/abc/def?qs=ghi#jkl"); }
+
+    // ---- bad
+
+    // Leading ':'
+    @Test public void bad_scheme_1() { bad(":segment"); }
+
+    // Bad scheme
+    @Test public void bad_scheme_2() { bad("://host/xyz"); }
+
+    // Bad scheme
+    @Test public void bad_scheme_3() { bad("1://host/xyz"); }
+
+    // Bad scheme
+    @Test public void bad_scheme_4() { bad("a~b://host/xyz"); }
+
+    // Bad scheme
+    @Test public void bad_scheme_5() { bad("aβ://host/xyz"); }
+
+    // Bad scheme
+    @Test public void bad_scheme_6() { bad("_:xyz"); }
+
+    // Bad scheme
+    @Test public void bad_scheme_7() { bad("a_b:xyz"); }
+
+    // Space!
+    @Test public void bad_chars_1() { bad("http://abcdef:80/xyz /abc"); }
+
+    // colons
+    @Test public void bad_host_1() { bad("http://abcdef:80:/xyz"); }
+
+    // Bad IPv6
+    @Test public void bad_ipv6_1() { bad("http://[::80/xyz"); }
+
+    // Bad IPv6
+    @Test public void bad_ipv6_2() { bad("http://host]/xyz"); }
+
+    // Bad IPv6
+    @Test public void bad_ipv6_3() { bad("http://[]/xyz"); }
+
+    // Multiple @
+    @Test public void bad_authority_1() { bad("ftp://abc@def@host/abc"); }
+
+    // Multiple colon in authority
+    @Test public void bad_authority_2() { bad("http://abc:def:80/abc"); }
+
+    // Bad %-encoding.
+    @Test public void bad_percent_1() { bad("http://example/abc%ZZdef"); }
+
+    @Test public void bad_percent_2() { bad("http://abc%ZZdef/"); }
+
+    // Bad %-encoded
+    @Test public void bad_percent_3() { bad("http://example/xyz%"); }
+
+    // Bad %-encoded
+    @Test public void bad_percent_4() { bad("http://example/xyz%A"); }
+
+    // Bad %-encoded
+    @Test public void bad_percent_5() { bad("http://example/xyz%A?"); }
+
+    // [] not allowed.
+    @Test public void bad_frag_1() { bad("http://eg.com/test.txt#xpointer(/unit[5])"); }
+
+    // ---- bad by scheme.
+    @Test public void parse_http_bad_01() { badSpecific("http://user@host:8081/abc/def?qs=ghi#jkl"); }
+
+    //  urn:2char:1char
+    // urn:NID:NSS where NID is at least 2 alphas, and at most 32 long
+    @Test public void parse_urn_bad_01() { badSpecific("urn:"); }
+    @Test public void parse_urn_bad_02() { badSpecific("urn:x:abc"); }
+
+    @Test public void parse_urn_bad_03() { badSpecific("urn:abc:"); }
+    // 33 chars
+    @Test public void parse_urn_bad_04() { badSpecific("urn:abcdefghij-123456789-123456789-yz:a"); }
+
+    // Bad by URN specific rule for the query components.
+    @Test public void parse_urn_bad_05()    { badSpecific("urn:local:abc/def?query=foo"); }
+
+    @Test public void parse_urn_uuid_bad_01() {
+        badSpecific("urn:uuid:06e775ac-2c38-11b2-801c-8086f2cc00c9?query=foo");
+    }
+
+    @Test public void parse_urn_uuid_bad_02() {
+        badSpecific("urn:uuid:06e775ac-2c38-11b2-801c-8086f2cc00c9#frag");
+    }
+
+    @Test public void parse_urn_uuid_bad_03() {
+        // Bad length
+        badSpecific("urn:uuid:06e775ac");
+    }
+
+    @Test public void parse_urn_uuid_bad_04() {
+        // Bad character
+        badSpecific("urn:uuid:06e775ac-ZZZZ-11b2-801c-8086f2cc00c9");
+    }
+
+    @Test public void parse_uuid_bad_01() {
+        badSpecific("uuid:06e775ac-2c38-11b2-801c-8086f2cc00c9?query=foo");
+    }
+
+    @Test public void parse_uuid_bad_02() {
+        badSpecific("uuid:06e775ac-2c38-11b2-801c-8086f2cc00c9#frag");
+    }
+
+    @Test public void parse_uuid_bad_03() {
+        badSpecific("uuid:06e775ac-2c38-11b2");
+    }
+
+    private void good(String string) {
+        IRIx iri = IRIx.create(string);
+        assertNotNull(iri);
+        if ( true ) {
+            IRI iri1 = JenaIRI.iriFactory().create(string);
+            if ( iri1.hasViolation(true) ) {
+                iri1.violations(true).forEachRemaining(v-> System.err.println("IRI = "+string + " :: "+v.getLongMessage()));
+                fail("Violations "+string);
+            }
+        }
+        java.net.URI javaURI = java.net.URI.create(string);
+        assertNotNull(javaURI);
+    }
+
+    private void goodNoIRICheck(String string) {
+        IRIx iri = IRIx.create(string);
+        java.net.URI javaURI = java.net.URI.create(string);
+    }
+
+    // Expect an IRIParseException
+    private void bad(String string) {
+        try {
+            IRIs.checkEx(string);
+            IRIs.reference(string);
+            //RFC3986.check(string);
+            fail("Did not fail: "+string);
+        } catch (IRIException ex) {}
+    }
+
+    private void badSpecific(String string) {
+        bad(string);
+    }
+}
diff --git a/jena-core/src/test/java/org/apache/jena/irix/TestRelative.java b/jena-core/src/test/java/org/apache/jena/irix/TestRelative.java
new file mode 100644
index 0000000..3c7ed87
--- /dev/null
+++ b/jena-core/src/test/java/org/apache/jena/irix/TestRelative.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.irix;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class TestRelative extends AbstractTestIRIx {
+
+    public TestRelative(String name, IRIProvider provider) {
+        super(name, provider);
+    }
+
+    @Test
+    public void relative_01() { testRelative("http://host/dir/", "http://host/dir/file", "file"); }
+
+    @Test
+    public void relative_02() { testRelative("http://host/dir/", "http://elsewhere/dir/file", null); }
+
+    @Test
+    public void relative_03() { testRelative("https://host/dir/", "http://host/dir/file", null); }
+
+    @Test
+    public void relative_04() { testRelative("http://host:1234/dir/", "http://host:1234/dir/file", "file"); }
+
+    @Test
+    public void relative_05() { testRelative("https://host:1234/dir/", "http://host:5678/dir/file", null); }
+
+    @Test
+    public void relative_06() { testRelative("http://ex/path/?query", "http://ex/path/file", null); }
+
+    @Test
+    public void relative_07() { testRelative("http://ex/path/#frag", "http://ex/path/file", "file"); }
+
+    @Test
+    public void relative_08() { testRelative("http://ex/path/", "http://ex/path/file?q=x", "file?q=x"); }
+
+    @Test
+    public void relative_09() { testRelative("http://ex/path/", "http://ex/path/file#frag", "file#frag"); }
+    @Test
+    public void relative_10() { testRelative("http://example/ns#", "http://example/x", "x") ; }
+
+    @Test
+    public void relative_11() { testRelative("http://example/ns#", "http://example/ns#x", "#x") ; }
+
+    private void testRelative(String baseStr, String pathStr, String expected) {
+        IRIx base = IRIx.create(baseStr);
+        IRIx path = IRIx.create(pathStr);
+        IRIx rel = base.relativize(path);
+        String result = (rel==null)?null:rel.str();
+        assertEquals(expected, result);
+        if ( expected != null ) {
+            IRIx path2 = base.resolve(rel);
+            assertEquals(path, path2);
+            assertEquals(path.str(), path2.str());
+        }
+    }
+}
+
diff --git a/jena-core/src/test/java/org/apache/jena/irix/TestResolve.java b/jena-core/src/test/java/org/apache/jena/irix/TestResolve.java
new file mode 100644
index 0000000..63625d6
--- /dev/null
+++ b/jena-core/src/test/java/org/apache/jena/irix/TestResolve.java
@@ -0,0 +1,206 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.irix;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class TestResolve extends AbstractTestIRIx {
+
+    public TestResolve(String name, IRIProvider provider) {
+        super(name, provider);
+    }
+
+    @Test
+    public void resolve_01() {
+        testResolve("http://example/dir/", "", "http://example/dir/");
+    }
+
+    @Test
+    public void resolve_02() {
+        testResolve("http://example/dir/", "A", "http://example/dir/A");
+    }
+
+    @Test
+    public void resolve_03() {
+        testResolve("http://example/dir", "A", "http://example/A");
+    }
+
+    @Test
+    public void resolve_04() {
+        testResolve("http://example/dir", "A/", "http://example/A/");
+    }
+
+    @Test
+    public void resolve_05() {
+        testResolve("http://example/dir1/dir2/dir3/dir4", "..", "http://example/dir1/dir2/");
+    }
+
+    @Test
+    public void resolve_06() {
+        testResolve("http://example/dir1/dir2/", "..", "http://example/dir1/");
+    }
+
+    @Test
+    public void resolve_07() {
+        testResolve("http://example/dir1/dir2", "..", "http://example/");
+    }
+
+    @Test
+    public void resolve_08() {
+        testResolve("http://example/dir1/dir2/f3", "..", "http://example/dir1/");
+    }
+
+    @Test
+    public void resolve_09() {
+        testResolve("http://example/dir1/dir2/", "../a", "http://example/dir1/a");
+    }
+
+    @Test
+    public void resolve_10() {
+        testResolve("http://example/dir1/dir2/f3", "../a", "http://example/dir1/a");
+    }
+
+    @Test
+    public void resolve_11() {
+        testResolve("http://example/dir1/f2", "../a", "http://example/a");
+    }
+
+    @Test
+    public void resolve_12() {
+        testResolve("http://example/dir1/dir2/", "..", "http://example/dir1/");
+    }
+
+    @Test
+    public void resolve_13() {
+        testResolve("http://example/dir/", "..", "http://example/");
+    }
+
+    @Test
+    public void resolve_14() {
+        testResolve("http://example/dir", "..", "http://example/");
+    }
+
+    @Test
+    public void resolve_15() {
+        testResolve("http://example/", "..", "http://example/");
+    }
+
+    @Test
+    public void resolve_16() {
+        testResolve("http://example", "..", "http://example/");
+    }
+
+    @Test
+    public void resolve_17() {
+        testResolve("http://example/path?query#frag", "http://host", "http://host");
+    }
+
+    @Test
+    public void resolve_18() {
+        testResolve("http://example/", "abc", "http://example/abc");
+    }
+
+    @Test
+    public void resolve_19() {
+        testResolve("http://example", "abc", "http://example/abc");
+    }
+
+    @Test
+    public void resolve_20() {
+        testResolve("http://example/dir/file", ".", "http://example/dir/");
+    }
+
+    @Test
+    public void resolve_21() {
+        testResolve("http://example/dir/", ".", "http://example/dir/");
+    }
+
+    @Test
+    public void resolve_22() {
+        testResolve("http://example/dir1/dir2/", ".", "http://example/dir1/dir2/");
+    }
+
+    @Test
+    public void resolve_23() {
+        testResolve("http://example/", ".", "http://example/");
+    }
+
+    @Test
+    public void resolve_24() {
+        testResolve("http://example/#fragment", "path?q=arg", "http://example/path?q=arg");
+    }
+
+    @Test
+    public void resolve_25() {
+        testResolve("http://example/", "../path?q=arg", "http://example/path?q=arg");
+    }
+
+    @Test
+    public void resolve_26() {
+        testResolve("http://example/?query", "../path?q=arg", "http://example/path?q=arg");
+    }
+
+    @Test
+    public void resolve_27() {
+        testResolve("http://example/?query", "../path?q=arg", "http://example/path?q=arg");
+    }
+
+    @Test
+    public void resolve_28() {
+        testResolve("http://example/path", "?query", "http://example/path?query");
+    }
+
+    @Test
+    public void resolve_29() {
+        testResolve("http://example/path", "#frag", "http://example/path#frag");
+    }
+
+    @Test
+    public void resolve_30() {
+        testResolve("http://example/path", "..#frag", "http://example/#frag");
+    }
+
+    @Test
+    public void resolve_31() {
+        testResolve("http://example/path#fragment", "..#frag", "http://example/#frag");
+    }
+
+    @Test
+    public void resolve_32() {
+        testResolve("http://example/dir1/dir2/", "/OtherPath", "http://example/OtherPath");
+    }
+
+    @Test
+    public void resolve_33() {
+        testResolve("http://example/dir1/dir2/", "//EX/OtherPath", "http://EX/OtherPath");
+    }
+
+    private void testResolve(String base, String rel, String expected) {
+        IRIx baseIRI = IRIx.create(base);
+        IRIx relIRI = IRIx.create(rel);
+        IRIx iri2 = baseIRI.resolve(relIRI);
+        String s1 = iri2.str();
+        assertEquals(expected, s1);
+    }
+}
diff --git a/jena-core/src/test/java/org/apache/jena/test/TestPackage.java b/jena-core/src/test/java/org/apache/jena/test/TestPackage.java
index ada5d77..e397a97 100644
--- a/jena-core/src/test/java/org/apache/jena/test/TestPackage.java
+++ b/jena-core/src/test/java/org/apache/jena/test/TestPackage.java
@@ -34,7 +34,7 @@ public class TestPackage extends TestCase {
         TestSuite ts = new TestSuite() ;
         ts.setName("Jena") ;
         addTest(ts,  "System setup", TestSystemSetup.suite());
-        addTest(ts,  "IRI", org.apache.jena.irix.TestIRIx.suite());
+        addTest(ts,  "IRI", org.apache.jena.irix.TS_IRIx.suite());
         addTest(ts,  "Enhanced", org.apache.jena.enhanced.test.TestPackage.suite());
         addTest(ts,  "Datatypes", org.apache.jena.datatypes.TestPackage.suite()) ;
         addTest(ts,  "Graph", org.apache.jena.graph.test.TestPackage.suite());
diff --git a/jena-iri/src/main/java/org/apache/jena/iri/ViolationCodes.java b/jena-iri/src/main/java/org/apache/jena/iri/ViolationCodes.java
index d2ebe54..105f272 100644
--- a/jena-iri/src/main/java/org/apache/jena/iri/ViolationCodes.java
+++ b/jena-iri/src/main/java/org/apache/jena/iri/ViolationCodes.java
@@ -2472,7 +2472,7 @@ This class is not part of the API.
                 // RFC 2141 -
                 //"(?![uU][rR][nN]:)[a-zA-Z0-9][-a-zA-Z0-9]{1,31}:[^/~]+"
                 // RFC 8141 revision of 2141 - JENA-1647
-                "(?![uU][rR][nN]:)[a-zA-Z0-9][-a-zA-Z0-9]{1,31}:.+"
+                "(?![uU][rR][nN]:)[a-zA-Z0-9][-a-zA-Z0-9]{0,30}[a-zA-Z0-9]:.+"
                 );
 
         spec.setReserved(PATH,"/~");