You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@metron.apache.org by ce...@apache.org on 2016/12/24 01:41:06 UTC

incubator-metron git commit: METRON-639: The Network Stellar functions need to have better unit testing closes apache/incubator-metron#402

Repository: incubator-metron
Updated Branches:
  refs/heads/master accfce8f9 -> f3376755c


METRON-639: The Network Stellar functions need to have better unit testing closes apache/incubator-metron#402


Project: http://git-wip-us.apache.org/repos/asf/incubator-metron/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-metron/commit/f3376755
Tree: http://git-wip-us.apache.org/repos/asf/incubator-metron/tree/f3376755
Diff: http://git-wip-us.apache.org/repos/asf/incubator-metron/diff/f3376755

Branch: refs/heads/master
Commit: f3376755c9df896ed5b0082faad4712d1e99b1ee
Parents: accfce8
Author: cstella <ce...@gmail.com>
Authored: Fri Dec 23 20:40:32 2016 -0500
Committer: cstella <ce...@gmail.com>
Committed: Fri Dec 23 20:40:32 2016 -0500

----------------------------------------------------------------------
 .../common/dsl/functions/NetworkFunctions.java  |  81 +++++++---
 .../stellar/network/NetworkFunctionsTest.java   | 155 +++++++++++++++++++
 .../stellar/network/RemoveSubdomainsTest.java   |  48 ------
 .../common/utils/StellarProcessorUtils.java     | 148 ++++++++++++++----
 4 files changed, 330 insertions(+), 102 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/f3376755/metron-platform/metron-common/src/main/java/org/apache/metron/common/dsl/functions/NetworkFunctions.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-common/src/main/java/org/apache/metron/common/dsl/functions/NetworkFunctions.java b/metron-platform/metron-common/src/main/java/org/apache/metron/common/dsl/functions/NetworkFunctions.java
index f33a4a5..2dc92c9 100644
--- a/metron-platform/metron-common/src/main/java/org/apache/metron/common/dsl/functions/NetworkFunctions.java
+++ b/metron-platform/metron-common/src/main/java/org/apache/metron/common/dsl/functions/NetworkFunctions.java
@@ -18,10 +18,10 @@
 
 package org.apache.metron.common.dsl.functions;
 
-import com.google.common.base.Joiner;
 import com.google.common.base.Splitter;
 import com.google.common.collect.Iterables;
 import com.google.common.net.InternetDomainName;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.net.util.SubnetUtils;
 import org.apache.metron.common.dsl.BaseStellarFunction;
 import org.apache.metron.common.dsl.Stellar;
@@ -29,7 +29,6 @@ import org.apache.metron.common.dsl.Stellar;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.List;
-import java.util.function.Function;
 
 public class NetworkFunctions {
   @Stellar(name="IN_SUBNET"
@@ -55,7 +54,7 @@ public class NetworkFunctions {
       }
       boolean inSubnet = false;
       for(int i = 1;i < list.size() && !inSubnet;++i) {
-        String cidr = (String) list.get(1);
+        String cidr = (String) list.get(i);
         if(cidr == null) {
           continue;
         }
@@ -86,15 +85,19 @@ public class NetworkFunctions {
       InternetDomainName idn = toDomainName(dnObj);
       if(idn != null) {
         String dn = dnObj.toString();
-        String tld = Joiner.on(".").join(idn.publicSuffix().parts());
-        String suffix = dn.substring(0, dn.length() - tld.length());
-        String hostnameWithoutTLD = suffix.substring(0, suffix.length() - 1);
-        String hostnameWithoutSubsAndTLD = Iterables.getLast(Splitter.on(".").split(hostnameWithoutTLD), null);
-        if(hostnameWithoutSubsAndTLD == null) {
-          return null;
+        String tld = extractTld(idn, dn);
+        if(!StringUtils.isEmpty(dn)) {
+          String suffix = safeSubstring(dn, 0, dn.length() - tld.length());
+          String hostnameWithoutTLD = safeSubstring(suffix, 0, suffix.length() - 1);
+          if(hostnameWithoutTLD == null) {
+            return dn;
+          }
+          String hostnameWithoutSubsAndTLD = Iterables.getLast(Splitter.on(".").split(hostnameWithoutTLD), null);
+          if(hostnameWithoutSubsAndTLD == null) {
+            return null;
+          }
+          return hostnameWithoutSubsAndTLD + "." + tld;
         }
-        return hostnameWithoutSubsAndTLD + "." + tld;
-
       }
       return null;
     }
@@ -116,14 +119,13 @@ public class NetworkFunctions {
       InternetDomainName idn = toDomainName(dnObj);
       if(idn != null) {
         String dn = dnObj.toString();
-        String tld = idn.publicSuffix().toString();
-        String suffix = Iterables.getFirst(Splitter.on(tld).split(dn), null);
-        if(suffix != null)
-        {
-          return suffix.substring(0, suffix.length() - 1);
+        String tld = extractTld(idn, dn);
+        String suffix = safeSubstring(dn, 0, dn.length() - tld.length());
+        if(StringUtils.isEmpty(suffix)) {
+          return suffix;
         }
         else {
-          return null;
+          return suffix.substring(0, suffix.length() - 1);
         }
       }
       return null;
@@ -144,10 +146,7 @@ public class NetworkFunctions {
     public Object apply(List<Object> objects) {
       Object dnObj = objects.get(0);
       InternetDomainName idn = toDomainName(dnObj);
-      if(idn != null) {
-        return idn.publicSuffix().toString();
-      }
-      return null;
+      return extractTld(idn, dnObj + "");
     }
   }
 
@@ -220,6 +219,43 @@ public class NetworkFunctions {
     }
   }
 
+  /**
+   * Extract the TLD.  If the domain is a normal domain, then we can handle the TLD via the InternetDomainName object.
+   * If it is not, then we default to returning the last segment after the final '.'
+   * @param idn
+   * @param dn
+   * @return The TLD of the domain
+   */
+  private static String extractTld(InternetDomainName idn, String dn) {
+
+    if(idn != null && idn.hasPublicSuffix()) {
+      return idn.publicSuffix().toString();
+    }
+    else if(dn != null) {
+      StringBuffer tld = new StringBuffer("");
+      for(int idx = dn.length() -1;idx >= 0;idx--) {
+        char c = dn.charAt(idx);
+        if(c == '.') {
+          break;
+        }
+        else {
+          tld.append(dn.charAt(idx));
+        }
+      }
+      return tld.reverse().toString();
+    }
+    else {
+      return null;
+    }
+  }
+
+  private static String safeSubstring(String val, int start, int end) {
+    if(!StringUtils.isEmpty(val)) {
+      return val.substring(start, end);
+    }
+    return null;
+  }
+
   private static InternetDomainName toDomainName(Object dnObj) {
     if(dnObj != null) {
       if(dnObj instanceof String) {
@@ -243,8 +279,9 @@ public class NetworkFunctions {
       return null;
     }
     if(urlObj instanceof String) {
+      String url = urlObj.toString();
       try {
-        return new URL(urlObj.toString());
+        return new URL(url);
       } catch (MalformedURLException e) {
         return null;
       }

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/f3376755/metron-platform/metron-common/src/test/java/org/apache/metron/common/stellar/network/NetworkFunctionsTest.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-common/src/test/java/org/apache/metron/common/stellar/network/NetworkFunctionsTest.java b/metron-platform/metron-common/src/test/java/org/apache/metron/common/stellar/network/NetworkFunctionsTest.java
new file mode 100644
index 0000000..783658c
--- /dev/null
+++ b/metron-platform/metron-common/src/test/java/org/apache/metron/common/stellar/network/NetworkFunctionsTest.java
@@ -0,0 +1,155 @@
+/**
+ * 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.metron.common.stellar.network;
+
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+
+
+import static org.apache.metron.common.utils.StellarProcessorUtils.runWithArguments;
+
+public class NetworkFunctionsTest {
+
+  @Test
+  public void inSubnetTest_positive() {
+    runWithArguments("IN_SUBNET", ImmutableList.of("192.168.0.1", "192.168.0.0/24"), true);
+  }
+
+  @Test
+  public void inSubnetTest_negative() {
+    runWithArguments("IN_SUBNET", ImmutableList.of("192.168.1.1", "192.168.0.0/24"), false);
+  }
+
+  @Test
+  public void inSubnetTest_multiple() {
+    runWithArguments("IN_SUBNET", ImmutableList.of("192.168.1.1", "192.168.0.0/24", "192.168.1.0/24"), true);
+  }
+
+  @Test
+  public void removeSubdomainsTest() {
+    runWithArguments("DOMAIN_REMOVE_SUBDOMAINS", "www.google.co.uk", "google.co.uk");
+    runWithArguments("DOMAIN_REMOVE_SUBDOMAINS", "www.google.com", "google.com");
+    runWithArguments("DOMAIN_REMOVE_SUBDOMAINS", "com", "com");
+  }
+
+  @Test
+  public void removeSubdomainsTest_tld_square() {
+    runWithArguments("DOMAIN_REMOVE_SUBDOMAINS", "com.com", "com.com");
+    runWithArguments("DOMAIN_REMOVE_SUBDOMAINS", "net.net", "net.net");
+    runWithArguments("DOMAIN_REMOVE_SUBDOMAINS", "co.uk.co.uk", "uk.co.uk");
+    runWithArguments("DOMAIN_REMOVE_SUBDOMAINS", "www.subdomain.com.com", "com.com");
+  }
+
+  @Test
+  public void removeSubdomainsTest_unknowntld() {
+    runWithArguments("DOMAIN_REMOVE_SUBDOMAINS", "www.subdomain.google.gmail", "google.gmail");
+  }
+
+  @Test
+  public void toTldTest() {
+    runWithArguments("DOMAIN_TO_TLD", "www.google.co.uk", "co.uk");
+    runWithArguments("DOMAIN_TO_TLD", "www.google.com", "com");
+    runWithArguments("DOMAIN_TO_TLD", "com", "com");
+  }
+
+  @Test
+  public void toTldTest_tld_square() {
+    runWithArguments("DOMAIN_TO_TLD", "com.com", "com");
+    runWithArguments("DOMAIN_TO_TLD", "net.net", "net");
+    runWithArguments("DOMAIN_TO_TLD", "co.uk.co.uk", "co.uk");
+    runWithArguments("DOMAIN_TO_TLD", "www.subdomain.com.com", "com");
+  }
+
+  @Test
+  public void toTldTest_unknowntld() {
+    runWithArguments("DOMAIN_TO_TLD", "www.subdomain.google.gmail", "gmail");
+  }
+
+  @Test
+  public void removeTldTest() {
+    runWithArguments("DOMAIN_REMOVE_TLD", "www.google.co.uk", "www.google");
+    runWithArguments("DOMAIN_REMOVE_TLD", "www.google.com", "www.google");
+    runWithArguments("DOMAIN_REMOVE_TLD", "com", "");
+  }
+
+  @Test
+  public void removeTldTest_tld_square() {
+    runWithArguments("DOMAIN_REMOVE_TLD", "com.com", "com");
+    runWithArguments("DOMAIN_REMOVE_TLD", "net.net", "net");
+    runWithArguments("DOMAIN_REMOVE_TLD", "co.uk.co.uk", "co.uk");
+    runWithArguments("DOMAIN_REMOVE_TLD", "www.subdomain.com.com", "www.subdomain.com");
+  }
+
+  @Test
+  public void removeTldTest_unknowntld() {
+    runWithArguments("DOMAIN_REMOVE_TLD", "www.subdomain.google.gmail", "www.subdomain.google");
+  }
+
+  @Test
+  public void urlToPortTest() {
+    runWithArguments("URL_TO_PORT", "http://www.google.com/foo/bar", 80);
+    runWithArguments("URL_TO_PORT", "https://www.google.com/foo/bar", 443);
+    runWithArguments("URL_TO_PORT", "http://www.google.com:7979/foo/bar", 7979);
+  }
+
+
+  @Test
+  public void urlToPortTest_unknowntld() {
+    runWithArguments("URL_TO_PORT", "http://www.google.gmail/foo/bar", 80);
+  }
+
+  @Test
+  public void urlToHostTest() {
+    runWithArguments("URL_TO_HOST", "http://www.google.com/foo/bar", "www.google.com");
+    runWithArguments("URL_TO_HOST", "https://www.google.com/foo/bar", "www.google.com");
+    runWithArguments("URL_TO_HOST", "http://www.google.com:7979/foo/bar", "www.google.com");
+    runWithArguments("URL_TO_HOST", "http://localhost:8080/a", "localhost");
+  }
+
+
+  @Test
+  public void urlToHostTest_unknowntld() {
+    runWithArguments("URL_TO_HOST", "http://www.google.gmail/foo/bar", "www.google.gmail");
+  }
+
+  @Test
+  public void urlToProtocolTest() {
+    runWithArguments("URL_TO_PROTOCOL", "http://www.google.com/foo/bar", "http");
+    runWithArguments("URL_TO_PROTOCOL", "https://www.google.com/foo/bar", "https");
+  }
+
+
+  @Test
+  public void urlToProtocolTest_unknowntld() {
+    runWithArguments("URL_TO_PROTOCOL", "http://www.google.gmail/foo/bar", "http");
+  }
+
+  @Test
+  public void urlToPathTest() {
+    runWithArguments("URL_TO_PATH", "http://www.google.com/foo/bar", "/foo/bar");
+    runWithArguments("URL_TO_PATH", "https://www.google.com/foo/bar", "/foo/bar");
+  }
+
+
+  @Test
+  public void urlToPathTest_unknowntld() {
+    runWithArguments("URL_TO_PATH", "http://www.google.gmail/foo/bar", "/foo/bar");
+  }
+
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/f3376755/metron-platform/metron-common/src/test/java/org/apache/metron/common/stellar/network/RemoveSubdomainsTest.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-common/src/test/java/org/apache/metron/common/stellar/network/RemoveSubdomainsTest.java b/metron-platform/metron-common/src/test/java/org/apache/metron/common/stellar/network/RemoveSubdomainsTest.java
deleted file mode 100644
index ab503e9..0000000
--- a/metron-platform/metron-common/src/test/java/org/apache/metron/common/stellar/network/RemoveSubdomainsTest.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/**
- * 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.metron.common.stellar.network;
-
-import com.google.common.collect.ImmutableList;
-import org.apache.metron.common.dsl.functions.NetworkFunctions;
-import org.junit.Assert;
-import org.junit.Test;
-
-public class RemoveSubdomainsTest {
-
-  @Test
-  public void testEdgeCasesTldSquared() {
-    NetworkFunctions.RemoveSubdomains removeSubdomains = new NetworkFunctions.RemoveSubdomains();
-    Assert.assertEquals("com.com", removeSubdomains.apply(ImmutableList.of("com.com")));
-    Assert.assertEquals("net.net", removeSubdomains.apply(ImmutableList.of("net.net")));
-    Assert.assertEquals("uk.co.uk", removeSubdomains.apply(ImmutableList.of("co.uk.co.uk")));
-    Assert.assertEquals("com.com", removeSubdomains.apply(ImmutableList.of("www.subdomain.com.com")));
-  }
-
-  @Test
-  public void testHappyPath() {
-    NetworkFunctions.RemoveSubdomains removeSubdomains = new NetworkFunctions.RemoveSubdomains();
-    Assert.assertEquals("example.com", removeSubdomains.apply(ImmutableList.of("my.example.com")));
-    Assert.assertEquals("example.co.uk", removeSubdomains.apply(ImmutableList.of("my.example.co.uk")));
-  }
-
-  @Test
-  public void testEmptyString() {
-    NetworkFunctions.RemoveSubdomains removeSubdomains = new NetworkFunctions.RemoveSubdomains();
-    Assert.assertEquals(null, removeSubdomains.apply(ImmutableList.of("")));
-  }
-}

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/f3376755/metron-platform/metron-common/src/test/java/org/apache/metron/common/utils/StellarProcessorUtils.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-common/src/test/java/org/apache/metron/common/utils/StellarProcessorUtils.java b/metron-platform/metron-common/src/test/java/org/apache/metron/common/utils/StellarProcessorUtils.java
index e7a58bf..7ef84ea 100644
--- a/metron-platform/metron-common/src/test/java/org/apache/metron/common/utils/StellarProcessorUtils.java
+++ b/metron-platform/metron-common/src/test/java/org/apache/metron/common/utils/StellarProcessorUtils.java
@@ -18,55 +18,139 @@
 
 package org.apache.metron.common.utils;
 
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
 import org.apache.metron.common.dsl.*;
 import org.apache.metron.common.stellar.StellarPredicateProcessor;
 import org.apache.metron.common.stellar.StellarProcessor;
 import org.junit.Assert;
 import org.junit.Test;
 
-import java.util.Map;
+import java.util.*;
+import java.util.function.Consumer;
+import java.util.function.IntConsumer;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
 
 public class StellarProcessorUtils {
 
-    /**
-     * This ensures the basic contract of a stellar expression is adhered to:
-     * 1. Validate works on the expression
-     * 2. The output can be serialized and deserialized properly
-     *
-     * @param rule
-     * @param variables
-     * @param context
-     * @return
-     */
-    public static Object run(String rule, Map<String, Object> variables, Context context) {
-        StellarProcessor processor = new StellarProcessor();
-        Assert.assertTrue(rule + " not valid.", processor.validate(rule, context));
-        Object ret = processor.parse(rule, x -> variables.get(x), StellarFunctions.FUNCTION_RESOLVER(), context);
-        byte[] raw = SerDeUtils.toBytes(ret);
-        Object actual = SerDeUtils.fromBytes(raw, Object.class);
-        Assert.assertEquals(ret, actual);
-        return ret;
-    }
+  /**
+   * This ensures the basic contract of a stellar expression is adhered to:
+   * 1. Validate works on the expression
+   * 2. The output can be serialized and deserialized properly
+   *
+   * @param rule
+   * @param variables
+   * @param context
+   * @return
+   */
+  public static Object run(String rule, Map<String, Object> variables, Context context) {
+    StellarProcessor processor = new StellarProcessor();
+    Assert.assertTrue(rule + " not valid.", processor.validate(rule, context));
+    Object ret = processor.parse(rule, x -> variables.get(x), StellarFunctions.FUNCTION_RESOLVER(), context);
+    byte[] raw = SerDeUtils.toBytes(ret);
+    Object actual = SerDeUtils.fromBytes(raw, Object.class);
+    Assert.assertEquals(ret, actual);
+    return ret;
+  }
+
+  public static Object run(String rule, Map<String, Object> variables) {
+    return run(rule, variables, Context.EMPTY_CONTEXT());
+  }
+
+  public static boolean runPredicate(String rule, Map resolver) {
+    return runPredicate(rule, resolver, Context.EMPTY_CONTEXT());
+  }
+
+  public static boolean runPredicate(String rule, Map resolver, Context context) {
+    return runPredicate(rule, new MapVariableResolver(resolver), context);
+  }
+
+  public static boolean runPredicate(String rule, VariableResolver resolver) {
+    return runPredicate(rule, resolver, Context.EMPTY_CONTEXT());
+  }
+
+  public static boolean runPredicate(String rule, VariableResolver resolver, Context context) {
+    StellarPredicateProcessor processor = new StellarPredicateProcessor();
+    Assert.assertTrue(rule + " not valid.", processor.validate(rule));
+    return processor.parse(rule, resolver, StellarFunctions.FUNCTION_RESOLVER(), context);
+  }
 
-    public static Object run(String rule, Map<String, Object> variables) {
-        return run(rule, variables, Context.EMPTY_CONTEXT());
+  public static void runWithArguments(String function, Object argument, Object expected) {
+    runWithArguments(function, ImmutableList.of(argument), expected);
+  }
+
+  public static void runWithArguments(String function, List<Object> arguments, Object expected) {
+    Supplier<Stream<Map.Entry<String, Object>>> kvStream = () -> StreamSupport.stream(new XRange(arguments.size()), false)
+            .map( i -> new AbstractMap.SimpleImmutableEntry<>("var" + i, arguments.get(i)));
+
+    String args = kvStream.get().map( kv -> kv.getKey())
+                                .collect(Collectors.joining(","));
+    Map<String, Object> variables = kvStream.get().collect(Collectors.toMap(kv -> kv.getKey(), kv -> kv.getValue()));
+    String stellarStatement =  function + "(" + args + ")";
+    String reason = stellarStatement + " != " + expected + " with variables: " + variables;
+
+    if(expected instanceof Double) {
+      Assert.assertEquals(reason, (Double)expected, (Double)run(stellarStatement, variables), 1e-6);
     }
+    else {
+      Assert.assertEquals(reason, expected, run(stellarStatement, variables));
+    }
+  }
+
+  public static class XRange extends Spliterators.AbstractIntSpliterator {
+    int end;
+    int i = 0;
 
-    public static boolean runPredicate(String rule, Map resolver) {
-        return runPredicate(rule, resolver, Context.EMPTY_CONTEXT());
+    public XRange(int start, int end) {
+      super(end - start, 0);
+      i = start;
+      this.end = end;
     }
 
-    public static boolean runPredicate(String rule, Map resolver, Context context) {
-        return runPredicate(rule, new MapVariableResolver(resolver), context);
+    public XRange(int end) {
+      this(0, end);
     }
 
-    public static boolean runPredicate(String rule, VariableResolver resolver) {
-        return runPredicate(rule, resolver, Context.EMPTY_CONTEXT());
+    @Override
+    public boolean tryAdvance(IntConsumer action) {
+      boolean isDone = i >= end;
+      if(isDone) {
+        return false;
+      }
+      else {
+        action.accept(i);
+        i++;
+        return true;
+      }
     }
 
-    public static boolean runPredicate(String rule, VariableResolver resolver, Context context) {
-        StellarPredicateProcessor processor = new StellarPredicateProcessor();
-        Assert.assertTrue(rule + " not valid.", processor.validate(rule));
-        return processor.parse(rule, resolver, StellarFunctions.FUNCTION_RESOLVER(), context);
+    /**
+     * {@inheritDoc}
+     *
+     * @param action
+     * @implSpec If the action is an instance of {@code IntConsumer} then it is cast
+     * to {@code IntConsumer} and passed to
+     * {@link #tryAdvance(IntConsumer)}; otherwise
+     * the action is adapted to an instance of {@code IntConsumer}, by
+     * boxing the argument of {@code IntConsumer}, and then passed to
+     * {@link #tryAdvance(IntConsumer)}.
+     */
+    @Override
+    public boolean tryAdvance(Consumer<? super Integer> action) {
+      boolean isDone = i >= end;
+      if(isDone) {
+        return false;
+      }
+      else {
+        action.accept(i);
+        i++;
+        return true;
+      }
     }
+  }
+
 }