From 0375c91027cfa293b2c2f2010ff5f4ed1399fa0b Mon Sep 17 00:00:00 2001
From: Christian Gram Kalhauge <chrg@dtu.dk>
Date: Tue, 9 Apr 2024 11:18:58 +0200
Subject: [PATCH] Before refactor

---
 src/main/java/jpamb/Runtime.java           | 85 ++++++++++++++--------
 src/main/java/jpamb/cases/Loops.java       | 41 +++++++++++
 src/main/java/jpamb/cases/Simple.java      |  9 +--
 src/main/java/jpamb/utils/Case.java        |  1 +
 src/main/java/jpamb/utils/CaseContent.java | 12 ++-
 src/main/java/jpamb/utils/Cases.java       |  9 +++
 src/main/java/jpamb/utils/Tag.java         | 13 ++--
 7 files changed, 126 insertions(+), 44 deletions(-)
 create mode 100644 src/main/java/jpamb/cases/Loops.java
 create mode 100644 src/main/java/jpamb/utils/Cases.java

diff --git a/src/main/java/jpamb/Runtime.java b/src/main/java/jpamb/Runtime.java
index 130f566..d895fa9 100644
--- a/src/main/java/jpamb/Runtime.java
+++ b/src/main/java/jpamb/Runtime.java
@@ -1,49 +1,72 @@
 package jpamb;
 
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
+import java.lang.reflect.*;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Stream;
 
-import jpamb.utils.Case;
-import jpamb.utils.CaseContent;
+import jpamb.utils.*;
+import jpamb.cases.*;
 
 public class Runtime {
 
-  public static void main(String[] args) throws ClassNotFoundException {
-    var cls = Class.forName("jpamb.cases.Simple");
-    for (Method m : cls.getMethods()) {
+  public static Case[] cases(Method m) {
+    var cases = m.getAnnotation(Cases.class);
+    if (cases == null) {
       var c = m.getAnnotation(Case.class);
       if (c == null)
-        continue;
+        return new Case[] {};
+      return new Case[] { c };
+    } else {
+      return cases.value();
+    }
+  }
 
-      CaseContent content;
-      content = CaseContent.parse(c.value());
+  public static void main(String[] args) throws ClassNotFoundException, InterruptedException {
+    var mths = Stream.of(Simple.class, Loops.class).flatMap(c -> Stream.of(c.getMethods())).toList();
+    for (Method m : mths) {
+      for (Case c : cases(m)) {
+        CaseContent content = CaseContent.parse(c.value());
 
-      if (!Modifier.isStatic(m.getModifiers())) {
-        System.out.println("Method is not static");
-        continue;
-      }
+        if (!Modifier.isStatic(m.getModifiers())) {
+          System.out.println("Method is not static");
+          continue;
+        }
+
+        String id = m.getDeclaringClass().getName() + "." + m.getName() + content + ":";
+        System.out.printf("%-80s", id);
+        System.out.flush();
+        final AtomicReference<Throwable> atom = new AtomicReference<>();
+        Thread t = new Thread(() -> {
+          try {
+            m.invoke(null, content.params());
+          } catch (InvocationTargetException e) {
+            atom.set(e.getCause());
+          } catch (IllegalAccessException e) {
+            atom.set(e);
+          }
+        });
+        t.start();
+        t.join(100);
+
+        if (t.isAlive())
+          t.stop();
 
-      String message = null;
-      try {
-        m.invoke(null, content.params());
-      } catch (IllegalAccessException e) {
-        e.printStackTrace();
-      } catch (InvocationTargetException e) {
-        Class<? extends Throwable> clazz = e.getCause().getClass();
-        if (content.result().expectThrows(clazz)) {
+        Throwable error = atom.get();
+        if (t.isAlive() && error == null) {
+          error = new TimeoutException();
+        }
+
+        String message;
+        if (error == null) {
+          message = "did not produce error";
+        } else if (content.result().expectThrows(error.getClass())) {
           message = "success";
         } else {
-          message = e.getCause().toString();
+          message = error.toString();
         }
+        System.out.printf("%s%n", message);
       }
-
-      if (message == null) {
-        message = "did not produce error";
-      }
-
-      System.out.println(cls.getName() + "." + m.getName() + content + ": " + message);
-
     }
   }
 }
diff --git a/src/main/java/jpamb/cases/Loops.java b/src/main/java/jpamb/cases/Loops.java
new file mode 100644
index 0000000..423d026
--- /dev/null
+++ b/src/main/java/jpamb/cases/Loops.java
@@ -0,0 +1,41 @@
+package jpamb.cases;
+
+import jpamb.utils.*;
+import static jpamb.utils.Tag.TagType.*;
+
+public class Loops {
+
+  @Case("() -> *")
+  @Tag({ LOOP })
+  public static void forever() {
+    while (true) {
+    }
+  }
+
+  @Case("() -> *")
+  @Tag({ LOOP })
+  public static void neverAsserts() {
+    int i = 1;
+    while (i > 0) {
+    }
+    assert false;
+  }
+
+  @Case("() -> *")
+  @Tag({ LOOP })
+  public static int neverDivides() {
+    int i = 1;
+    while (i > 0) {
+    }
+    return 0 / 0;
+  }
+
+  @Case("() -> assertion error")
+  @Tag({ INTEGER_OVERFLOW })
+  public static void terminates() {
+    short i = 0;
+    while (i++ != 0) {
+    }
+    assert false;
+  }
+}
diff --git a/src/main/java/jpamb/cases/Simple.java b/src/main/java/jpamb/cases/Simple.java
index f0742ca..fe2fb8c 100644
--- a/src/main/java/jpamb/cases/Simple.java
+++ b/src/main/java/jpamb/cases/Simple.java
@@ -39,12 +39,11 @@ public class Simple {
     return a / b;
   }
 
+  @Case("(false) -> assertion error")
   @Case("(true) -> divide by zero")
-  public static int divideByZeroIf(boolean b) {
-    if (b) {
-      return 1 / 0;
-    }
-    return 0;
+  public static int multiError(boolean b) {
+    assert b;
+    return 1 / 0;
   }
 
 }
diff --git a/src/main/java/jpamb/utils/Case.java b/src/main/java/jpamb/utils/Case.java
index e610bad..d834145 100644
--- a/src/main/java/jpamb/utils/Case.java
+++ b/src/main/java/jpamb/utils/Case.java
@@ -2,6 +2,7 @@ package jpamb.utils;
 
 import java.lang.annotation.*;
 
+@Repeatable(Cases.class)
 @Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.METHOD)
 public @interface Case {
diff --git a/src/main/java/jpamb/utils/CaseContent.java b/src/main/java/jpamb/utils/CaseContent.java
index 4a59beb..39b5299 100644
--- a/src/main/java/jpamb/utils/CaseContent.java
+++ b/src/main/java/jpamb/utils/CaseContent.java
@@ -5,6 +5,7 @@ import java.util.Arrays;
 import java.util.List;
 import java.util.Locale;
 import java.util.Scanner;
+import java.util.concurrent.TimeoutException;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -51,10 +52,13 @@ public record CaseContent(
 
   public static enum ResultType {
     DIVIDE_BY_ZERO,
-    ASSERTION_ERROR;
+    ASSERTION_ERROR,
+    NON_TERMINATION;
 
     public static ResultType parse(String string) {
-      if (string.equals("assertion error")) {
+      if (string.equals("*")) {
+        return NON_TERMINATION;
+      } else if (string.equals("assertion error")) {
         return ASSERTION_ERROR;
       } else if (string.equals("divide by zero")) {
         return DIVIDE_BY_ZERO;
@@ -69,6 +73,8 @@ public record CaseContent(
           return "divide by zero";
         case ASSERTION_ERROR:
           return "assertion error";
+        case NON_TERMINATION:
+          return "*";
         default:
           throw new RuntimeException("Unexpected");
       }
@@ -80,6 +86,8 @@ public record CaseContent(
           return clazz.equals(ArithmeticException.class);
         case ASSERTION_ERROR:
           return clazz.equals(AssertionError.class);
+        case NON_TERMINATION:
+          return clazz.equals(TimeoutException.class);
         default:
           throw new RuntimeException("Unexpected");
       }
diff --git a/src/main/java/jpamb/utils/Cases.java b/src/main/java/jpamb/utils/Cases.java
new file mode 100644
index 0000000..7b895e3
--- /dev/null
+++ b/src/main/java/jpamb/utils/Cases.java
@@ -0,0 +1,9 @@
+package jpamb.utils;
+
+import java.lang.annotation.*;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface Cases {
+  Case[] value();
+}
diff --git a/src/main/java/jpamb/utils/Tag.java b/src/main/java/jpamb/utils/Tag.java
index 9c0e093..139449f 100644
--- a/src/main/java/jpamb/utils/Tag.java
+++ b/src/main/java/jpamb/utils/Tag.java
@@ -6,11 +6,12 @@ import java.lang.annotation.*;
 @Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.METHOD)
 public @interface Tag {
-    TagType[] value();
+  TagType[] value();
 
-    public static enum TagType {
-      CONDITIONAL,
-      LOOP,
-      CALL
-    }
+  public static enum TagType {
+    CONDITIONAL,
+    LOOP,
+    INTEGER_OVERFLOW,
+    CALL
+  }
 }
-- 
GitLab