diff --git a/pages/docs/changelog.md b/pages/docs/changelog.md
index 138ad9b2fae4140e8d172635202a089d12d7e4b6..1fcc26bc7052a700a13b934b0e4c2ee32bb2b6d9 100644
--- a/pages/docs/changelog.md
+++ b/pages/docs/changelog.md
@@ -7,6 +7,7 @@
 ### Development Changes
 
 - Bugfix: "error: variable handler is already defined" when using multiple protocols [#58](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/58)
+- Bugfix: Inherited components of a type can not be chosen as port targets [#59](https://git-st.inf.tu-dresden.de/jastadd/ragconnect/-/issues/59)
 
 ## 1.0.0
 
diff --git a/pages/docs/compiler.md b/pages/docs/compiler.md
index 130020ad63b878c254bfa8f8fc6db82e71a3414d..ccff6d6c05f7544ff75caeda4f19a6f87c765ad9 100644
--- a/pages/docs/compiler.md
+++ b/pages/docs/compiler.md
@@ -75,8 +75,9 @@ However, depending on the selected protocols and/or used features, additional de
 - Required runtime dependencies: _none_
 - Required options for RelAST compiler: _none_
 - Required options for JastAdd:
-    - `--incremental`
-    - `--tracing=flush`
+    - `--incremental=param` (enable incremental evaluation)
+    - `--tracing=flush` (enable tracing of events)
+    - `--cache=all` (set all attributes to be cached)
 - Remarks:
     - Other (additional) values passed to those two options must be equal (e.g., `--incremental=param` passed to RagConnect must be also passed to JastAdd)
     - Other values besides `flush` can be added to `--tracing`
diff --git a/ragconnect.base/src/main/jastadd/Analysis.jrag b/ragconnect.base/src/main/jastadd/Analysis.jrag
index 79f37b38b3839982f784f1dd79b094a663aaa921..e1ec0f28977748a6b2d896c0c82c58c116f0081c 100644
--- a/ragconnect.base/src/main/jastadd/Analysis.jrag
+++ b/ragconnect.base/src/main/jastadd/Analysis.jrag
@@ -98,10 +98,22 @@ aspect Analysis {
   eq ContextFreeTypePortTarget.hasAttributeResetMethod() = false;
 
   // --- needProxyToken ---
-  syn boolean TokenComponent.needProxyToken() = !getDependencySourceDefinitionList().isEmpty() ||
-          getTokenPortTargetList().stream()
-                  .map(PortTarget::containingPortDefinition)
-                  .anyMatch(PortDefinition::shouldNotResetValue);
+  syn boolean TokenComponent.needProxyToken() {
+    for (Component comp : meOwnedByOthers()) {
+      TokenComponent tokenComp = comp.asTokenComponent();
+      if (tokenComp.directNeedProxyToken()) {
+        return true;
+      }
+    }
+    return directNeedProxyToken();
+  }
+
+  syn boolean TokenComponent.directNeedProxyToken() {
+    return !getDependencySourceDefinitionList().isEmpty() ||
+              getTokenPortTargetList().stream()
+                .map(PortTarget::containingPortDefinition)
+                .anyMatch(PortDefinition::shouldNotResetValue);
+  }
 
   // --- effectiveUsedAt ---
   coll Set<PortDefinition> MappingDefinition.effectiveUsedAt()
diff --git a/ragconnect.base/src/main/jastadd/Intermediate.jadd b/ragconnect.base/src/main/jastadd/Intermediate.jadd
index 97cc5148e9e6901d8a1b23cc965adbd57a7c0d44..60fe493797256563e7bfca17a6c35d341acfb8c0 100644
--- a/ragconnect.base/src/main/jastadd/Intermediate.jadd
+++ b/ragconnect.base/src/main/jastadd/Intermediate.jadd
@@ -358,7 +358,14 @@ aspect MustacheRagConnect {
   eq ContextFreeTypePortTarget.impliedPortDefinitions() {
     JastAddList<PortDefinition> result = super.impliedPortDefinitions();
     PortDefinition containingDef = containingPortDefinition();
+    Set<TypeComponent> seenTypeComponents = new HashSet<>();
     for (TypeComponent typeComponent : getTypeDecl().occurencesInProductionRules()) {
+      // only process each once
+      if (seenTypeComponents.contains(typeComponent)) {
+        continue;
+      }
+      seenTypeComponents.add(typeComponent);
+
       List<PortDefinition> defsForTypeComponent = lookupGivenTypePortDefinitions(typeComponent);
       if (!defsForTypeComponent.stream().anyMatch(containingDef::matchesType)) {
         // there is no user-defined port definition for this typeComponent yet
@@ -398,7 +405,7 @@ aspect MustacheReceiveAndSendAndHandleUri {
   syn String PortDefinition.connectParameterName() = "uriString";
 
   syn String PortDefinition.disconnectMethodName() {
-    // if both (send and receive) are defined for an port, ensure methods with different names
+    // if both (send and receive) are defined for a port, ensure methods with different names
     String extra;
     if (getPortTarget().isTokenPortTarget()) {
       extra = lookupTokenPortDefinitions(token()).size() > 1 ? uniqueSuffix() : "";
@@ -625,17 +632,27 @@ containingPortDefinition().getIndexBasedListAccess());
 
 aspect MustacheTokenComponent {
   // === TokenComponent ===
+  syn java.util.List<DependencyDefinition> TokenComponent.dependencySourceDefinitionsOwnedByMe() {
+    java.util.List<DependencyDefinition> result = new java.util.ArrayList<>();
+    result.addAll(getDependencySourceDefinitions());
+    for (Component comp : meOwnedByOthers()) {
+      result.addAll(comp.asTokenComponent().getDependencySourceDefinitions());
+    }
+    return result;
+  }
+
   syn String TokenComponent.internalName() = needProxyToken() ? ragconnect().internalRagConnectPrefix() + getName() : getName();
 
   syn String TokenComponent.javaType() = effectiveJavaTypeUse().prettyPrint();
 
   syn PortDefinition TokenComponent.normalTokenSendDef() {
-    for (PortTarget target : getTokenPortTargetList()) {
-      if (target.isTokenPortTarget() && target.containingPortDefinition().shouldNotResetValue()) {
-        return target.containingPortDefinition();
+    for (Component comp : meOwnedByOthers()) {
+      PortDefinition maybeResult = comp.asTokenComponent().directNormalTokenSendDef();
+      if (maybeResult != null) {
+        return maybeResult;
       }
     }
-    return null;
+    return directNormalTokenSendDef();
   }
 
   syn String TokenComponent.parentTypeName() = containingTypeDecl().getName();
@@ -663,6 +680,16 @@ aspect MustacheTokenComponent {
   }
 
   // > see MustacheSend for updateMethodName, writeMethodName
+
+  // === attributes needed for computing above ones ===
+  syn PortDefinition TokenComponent.directNormalTokenSendDef() {
+    for (PortTarget target : getTokenPortTargetList()) {
+      if (target.isTokenPortTarget() && target.containingPortDefinition().shouldNotResetValue()) {
+        return target.containingPortDefinition();
+      }
+    }
+    return null;
+  }
 }
 
 aspect MustacheTypeDecl {
@@ -670,6 +697,10 @@ aspect MustacheTypeDecl {
   syn String TypeComponent.parentTypeName() = containingTypeDecl().getName();
   syn String TypeComponent.disconnectMethodName() {
     List<TypePortTarget> typePortTargets = getTypePortTargets();
+    while (typePortTargets.isEmpty() && getRealComponent() != null) {
+      // get for "real" typeComponent (of super-type) and use its getTypePortTargets
+      typePortTargets = getRealComponent().asTypeComponent().getTypePortTargets();
+    }
     if (typePortTargets.isEmpty()) {
       return "MISSING_PORT";
     } else {
@@ -681,7 +712,7 @@ aspect MustacheTypeDecl {
   syn List<TypeComponent> TypeDecl.occurencesInProductionRules() {
     List<TypeComponent> result = new ArrayList<>();
     for (TypeDecl typeDecl : program().typeDecls()) {
-      for (Component comp : typeDecl.getComponentList()) {
+      for (Component comp : typeDecl.allComponentsAsOwnedByMe()) {
         if (comp.isTypeComponent() && comp.asTypeComponent().getTypeDecl().equals(this)) {
           result.add(comp.asTypeComponent());
         }
diff --git a/ragconnect.base/src/main/jastadd/IntermediateToYAML.jrag b/ragconnect.base/src/main/jastadd/IntermediateToYAML.jrag
index c247d9d0a69b2f548e04fe69adcf35620fd69255..5aa0f8c5fb1d007e6dfaca1b0fd70cf7566d810b 100644
--- a/ragconnect.base/src/main/jastadd/IntermediateToYAML.jrag
+++ b/ragconnect.base/src/main/jastadd/IntermediateToYAML.jrag
@@ -181,7 +181,8 @@ aspect IntermediateToYAML {
     result.put("DependencySourceDefinitions" , dependencySourceDefinitions);
 
     result.put("javaType" , javaType());
-    result.put("normalTokenSendDef" , normalTokenSendDef().toYAML());
+    result.put("normalTokenSendDef" , ASTNode.<PortDefinition,MappingElement>safeCall(
+            normalTokenSendDef(), def -> def.toYAML()));
     result.put("parentTypeName" , parentTypeName());
     return result;
   }
@@ -209,6 +210,29 @@ aspect IntermediateToYAML {
   protected StringElement ASTNode.sanitizeValueForYAML(String value) {
     return StringElement.of(value.replace("\"" , "\\\"").replace("\n" , "\\n"));
   }
+
+  // FIXME: remove refine once fixed in upstream mustache
+  refine Printing protected StringBuilder KeyValuePair.prettyPrint(StringBuilder sb, boolean printIndent, String indent) {
+    if (printIndent) sb.append(indent);
+    if (isCollapsed()) {
+      sb.append("\"");
+    }
+    sb.append(getKey());
+    if (isCollapsed()) {
+      sb.append("\"");
+    }
+    sb.append(":");
+    if (getValue() == null) {
+      sb.append(" null");
+    } else if (getValue().isComplexElement() && !getValue().isEmpty() && !getValue().isCollapsed()) {
+      sb.append("\n");
+      getValue().prettyPrint(sb, true, indent + PRINT_INDENT);
+    } else {
+      sb.append(" ");
+      getValue().prettyPrint(sb, false, indent);
+    }
+    return sb;
+  }
 }
 
 aspect Navigation {
diff --git a/ragconnect.base/src/main/jastadd/NameResolution.jrag b/ragconnect.base/src/main/jastadd/NameResolution.jrag
index e470430d8f5d355cd90d4e79503ee431de994a18..18a35f7295b63225f7695c045604df713950fbe0 100644
--- a/ragconnect.base/src/main/jastadd/NameResolution.jrag
+++ b/ragconnect.base/src/main/jastadd/NameResolution.jrag
@@ -27,7 +27,7 @@ aspect RagConnectNameResolution {
     java.util.List<PortDefinition> result = new java.util.ArrayList<>();
     for (PortDefinition def : onlyGiven ? givenPortDefinitionList() : allPortDefinitionList()) {
       PortTarget target = def.getPortTarget();
-      if (target.isTypePortTarget() && target.asTypePortTarget().getType().equals(type)) {
+      if (target.isTypePortTarget() && target.asTypePortTarget().getType().matches(type) && target.asTypePortTarget().getType().containingTypeDecl().equals(type.containingTypeDecl())) {
         result.add(def);
       }
     }
@@ -91,7 +91,7 @@ aspect RagConnectNameResolution {
     String childTypeName = id.substring(dotIndex + 1);
     TypeDecl type = program().resolveTypeDecl(parentTypeName);
     // iterate over components and find the matching typeComponent
-    for (Component comp : type.getComponentList()) {
+    for (Component comp : type.allComponentsAsOwnedByMe()) {
       if (comp.isTypeComponent() && comp.getName().equals(childTypeName)) {
         return comp.asTypeComponent();
       }
@@ -114,7 +114,7 @@ aspect RagConnectNameResolution {
     String childTypeName = id.substring(dotIndex + 1);
     TypeDecl type = program().resolveTypeDecl(parentTypeName);
     // iterate over components and find the matching typeComponent
-    for (Component comp : type.getComponentList()) {
+    for (Component comp : type.allComponentsAsOwnedByMe()) {
       if (comp.getName().equals(childTypeName)) {
         return comp;
       }
@@ -138,7 +138,7 @@ aspect RagConnectNameResolution {
     String tokenName = id.substring(dotIndex + 1);
     TypeDecl type = program().resolveTypeDecl(typeName);
     // iterate over components and find the matching tokenComponent
-    for (Component comp : type.getComponentList()) {
+    for (Component comp : type.allComponentsAsOwnedByMe()) {
       if (comp.isTokenComponent() && comp.getName().equals(tokenName)) {
         return comp.asTokenComponent();
       }
@@ -175,10 +175,63 @@ aspect RagConnectNameResolution {
     }
     return null;
   }
+}
 
+aspect RelastNameResolution {
   syn boolean Role.matches(String typeName, String roleName) = false;
   eq NavigableRole.matches(String typeName, String roleName) {
     return getType().getName().equals(typeName) && getName().equals(roleName);
   }
 
+  /** Returns all components including inherited ones */
+  syn List<Component> TypeDecl.allComponents() {
+    List<Component> result = new ArrayList<>();
+    getComponentList().forEach(result::add);
+    if (hasSuperType()) {
+      mergeComponentLists(result, getSuperType().allComponents());
+    }
+    return result;
+  }
+
+  /** Returns same as allComponents() but for each component the containingTypeDecl will compute to this component */
+  syn nta JastAddList<Component> TypeDecl.allComponentsAsOwnedByMe() {
+    JastAddList<Component> result = new JastAddList<>();
+    for (Component comp : allComponents()) {
+      Component newComp = comp.treeCopy();
+      newComp.setRealComponent(comp);
+      result.add(newComp);
+    }
+    return result;
+  }
+
+  public static void TypeDecl.mergeComponentLists(List<Component> subTypeResult, List<Component> superTypeResult) {
+    for (int i = superTypeResult.size() - 1; i >= 0; i--) {
+      Component superTypeComponent = superTypeResult.get(i);
+      if (subTypeResult.stream().noneMatch(subTypeComponent -> subTypeComponent.matches(superTypeComponent))) {
+        subTypeResult.add(0, superTypeComponent);
+      }
+    }
+  }
+
+  syn boolean Component.matches(Component other) = false;
+  eq TokenComponent.matches(Component other) {
+    if (!other.isTokenComponent()) { return false; }
+    return getName().equals(other.getName()) && (getJavaTypeUse() == null && other.asTokenComponent().getJavaTypeUse() == null || getJavaTypeUse().prettyPrint().equals(other.asTokenComponent().getJavaTypeUse().prettyPrint()));
+  }
+  eq TypeComponent.matches(Component other) = matchesNameAndType(other);
+  eq NormalComponent.matches(Component other) {
+    if (!other.isTypeComponent() || !other.asTypeComponent().isNormalComponent()) { return false; }
+    return super.matches(other);
+  }
+  eq ListComponent.matches(Component other) {
+    if (!other.isTypeComponent() || !other.asTypeComponent().isListComponent()) { return false; }
+    return super.matches(other);
+  }
+  eq OptComponent.matches(Component other) {
+    if (!other.isTypeComponent() || !other.asTypeComponent().isOptComponent()) { return false; }
+    return super.matches(other);
+  }
+  syn boolean TypeComponent.matchesNameAndType(Component other) {
+    return getName().equals(other.getName()) && getTypeDecl().equals(other.asTypeComponent().getTypeDecl());
+  }
 }
diff --git a/ragconnect.base/src/main/jastadd/Navigation.jrag b/ragconnect.base/src/main/jastadd/Navigation.jrag
index 8b23c000fb8136af504d6dc452a42d5dd6ae82ca..ffa4596e54ca19295c1c886eecf2276e38e16497 100644
--- a/ragconnect.base/src/main/jastadd/Navigation.jrag
+++ b/ragconnect.base/src/main/jastadd/Navigation.jrag
@@ -175,4 +175,25 @@ aspect RagConnectNavigation {
 
   // --- isListComponent --- (defined in PP, but only on TypeComponent)
   syn boolean Component.isListComponent() = false;
+
+  syn Set<Component> Component.meOwnedByOthers() {
+    Set<Component> result = new HashSet<>();
+    Deque<TypeDecl> todo = new ArrayDeque<>();
+    todo.add(containingTypeDecl());
+    while (!todo.isEmpty()) {
+      TypeDecl current = todo.pop();
+      todo.addAll(current.getSubTypeList());
+
+      for (Component comp : current.allComponentsAsOwnedByMe()) {
+        if (comp.matches(this)) {
+          result.add(comp);
+        }
+      }
+    }
+    return result;
+  }
+}
+aspect RelastNavigation {
+  // TODO only token-components from the nta "owned-by-me" must be added to Program.allTokenComponents
+  //TypeDecl contributes nta allComponentsAsOwnedByMe() to Program.allTokenComponents();
 }
diff --git a/ragconnect.base/src/main/jastadd/RagConnect.relast b/ragconnect.base/src/main/jastadd/RagConnect.relast
index 7282bb3fda5771c3b1ea87a4bfae235c3950b640..60f08491d4ac721c4dedb7a37280b1cdcdd385a3 100644
--- a/ragconnect.base/src/main/jastadd/RagConnect.relast
+++ b/ragconnect.base/src/main/jastadd/RagConnect.relast
@@ -47,3 +47,5 @@ Configuration ::=
 <EvaluationCounter:boolean>
 <ExperimentalJastAdd329:boolean>;
 rel Configuration.RootNode -> TypeDecl ;
+
+rel Component.RealComponent? -> Component ;
diff --git a/ragconnect.base/src/main/jastadd/Util.jadd b/ragconnect.base/src/main/jastadd/Util.jadd
index 174f8cdb9e40db6f3b1edef519b31daade2c1bba..7c8f5e65ac603fdc38a49b1961286b566f097b54 100644
--- a/ragconnect.base/src/main/jastadd/Util.jadd
+++ b/ragconnect.base/src/main/jastadd/Util.jadd
@@ -4,6 +4,9 @@ aspect Util {
     if (s.isEmpty()) return "";
     return Character.toUpperCase(s.charAt(0)) + s.substring(1);
   }
+  static <Input,Result> Result ASTNode.safeCall(Input input, java.util.function.Function<Input, Result> function) {
+    return input == null ? null : function.apply(input);
+  }
   protected T JastAddList.firstChild() { return getChild(0); }
   protected T JastAddList.lastChild() { return getChild(getNumChild() - 1); }
 
diff --git a/ragconnect.base/src/main/resources/RagConnectObserver.mustache b/ragconnect.base/src/main/resources/RagConnectObserver.mustache
index 24ed61c1a2ed6ceabec733b45f76a51eeae5f1e0..4414782b3dd1eb6e3e93ce4fe90fc35462416b79 100644
--- a/ragconnect.base/src/main/resources/RagConnectObserver.mustache
+++ b/ragconnect.base/src/main/resources/RagConnectObserver.mustache
@@ -119,8 +119,8 @@ aspect RagConnectObserver {
         }
         RagConnectObserverStartEntry startEntry = startEntries.peekFirst();
         if (node == startEntry.node &&
-            attribute == startEntry.attributeString &&
-            value == startEntry.flushIncToken) {
+                java.util.Objects.equals(attribute, startEntry.attributeString) &&
+                java.util.Objects.equals(value, startEntry.flushIncToken)) {
           // create a copy of the queue to avoid entering this again causing an endless recursion
           RagConnectObserverEntry[] entriesToProcess = entryQueue.toArray(new RagConnectObserverEntry[entryQueue.size()]);
           entryQueue.clear();
diff --git a/ragconnect.base/src/main/resources/ragconnect.mustache b/ragconnect.base/src/main/resources/ragconnect.mustache
index 5bd0166a46bb6f02be2df10a2bfd26b9cc63d508..a38ac9b3151227d92dffb83365eef8a1ab27f49e 100644
--- a/ragconnect.base/src/main/resources/ragconnect.mustache
+++ b/ragconnect.base/src/main/resources/ragconnect.mustache
@@ -91,6 +91,7 @@ aspect RagConnect {
     mayHaveRewrite();
     // check if --incremental is active
     Object checkIncremental = inc_throwAway_visited;
+    {{! TODO maybe check for something like _ragconnect_mqttHandler_computed to see if --cache=all }}
   {{/configIncrementalOptionActive}}
   }
 }
diff --git a/ragconnect.base/src/main/resources/tokenComponent.mustache b/ragconnect.base/src/main/resources/tokenComponent.mustache
index 12238f4428bf2ef79951015425cf45285cca624b..3d90c34b5ccfd3844972ef535844f8b042e1e3f1 100644
--- a/ragconnect.base/src/main/resources/tokenComponent.mustache
+++ b/ragconnect.base/src/main/resources/tokenComponent.mustache
@@ -1,6 +1,6 @@
 public {{parentTypeName}} {{parentTypeName}}.set{{Name}}({{javaType}} value) {
   set{{internalName}}(value);
-  {{#DependencySourceDefinitions}}
+  {{#dependencySourceDefinitionsOwnedByMe}}
   for ({{targetParentTypeName}} target : get{{internalRelationPrefix}}TargetList()) {
     {{#targetPortDefinition}}
     if (target.{{updateMethodName}}()) {
@@ -8,7 +8,7 @@ public {{parentTypeName}} {{parentTypeName}}.set{{Name}}({{javaType}} value) {
     }
     {{/targetPortDefinition}}
   }
-  {{/DependencySourceDefinitions}}
+  {{/dependencySourceDefinitionsOwnedByMe}}
   {{#normalTokenSendDef}}
   if ({{updateMethodName}}()) {
     {{writeMethodName}}();
diff --git a/ragconnect.tests/src/test/01-input/passing/README.md b/ragconnect.tests/src/test/01-input/passing/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..15afc42285d80e7e205777af6333162100cb36dd
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/passing/README.md
@@ -0,0 +1,4 @@
+# Passing
+
+This directory contains use case for RagConnect, that just need to pass compiling (no dedicated Java test).
+
diff --git a/ragconnect.tests/src/test/01-input/passing/inheritance/Test.connect b/ragconnect.tests/src/test/01-input/passing/inheritance/Test.connect
new file mode 100644
index 0000000000000000000000000000000000000000..3b4690cd05838159de6e5c9be0e47808ca25f9ab
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/passing/inheritance/Test.connect
@@ -0,0 +1,18 @@
+// both directions for SpecialA1
+send SpecialA1.Input ;
+receive SpecialA1.Input ;
+
+send SpecialA1.Many ;
+receive SpecialA1.Many ;
+
+send SpecialA1.Maybe ;
+receive SpecialA1.Maybe ;
+
+send SpecialA1.B ;
+receive SpecialA1.B ;
+
+// only one direction for SpecialA2
+send SpecialA2.Input ;
+send SpecialA2.Many ;
+send SpecialA2.Maybe ;
+send SpecialA2.B ;
diff --git a/ragconnect.tests/src/test/01-input/passing/inheritance/Test.relast b/ragconnect.tests/src/test/01-input/passing/inheritance/Test.relast
new file mode 100644
index 0000000000000000000000000000000000000000..7de55c8642cbb307c615e7abe78833f7e7ff1a6c
--- /dev/null
+++ b/ragconnect.tests/src/test/01-input/passing/inheritance/Test.relast
@@ -0,0 +1,5 @@
+Root ::= A ;
+A ::= <Input:String> Many:B* [Maybe:B] B ;
+SpecialA1 : A ;
+SpecialA2 : A ;
+B ::= ;
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AbstractCompilerTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AbstractCompilerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..002f9815f9eb09b8b41d533c9f01d6dcc5f0e98f
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/AbstractCompilerTest.java
@@ -0,0 +1,61 @@
+package org.jastadd.ragconnect.tests;
+
+import org.jastadd.ragconnect.tests.utils.TestUtils;
+import org.junit.jupiter.api.BeforeEach;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.jastadd.ragconnect.tests.utils.TestUtils.readFile;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * TODO: Add description.
+ *
+ * @author rschoene - Initial contribution
+ */
+public abstract class AbstractCompilerTest extends RagConnectTest {
+
+  protected abstract String getDirectoryName();
+
+  protected String getOutputDirectory() {
+    return TestUtils.OUTPUT_DIRECTORY_PREFIX + getDirectoryName();
+  }
+
+  protected abstract String getDefaultGrammarName();
+
+  @BeforeEach
+  public void ensureOutputDirectory() {
+    File outputDirectory = new File(getOutputDirectory());
+    assertTrue((outputDirectory.exists() && outputDirectory.isDirectory()) || outputDirectory.mkdir());
+  }
+
+  protected Path test(String inputDirectoryName, int expectedReturnValue, String rootNode, String... connectNames) throws IOException {
+    String grammarFile = Paths.get(inputDirectoryName, getDefaultGrammarName() + ".relast").toString();
+    List<String> connectFiles = Arrays.stream(connectNames)
+            .map(connectName -> Paths.get(inputDirectoryName,connectName + ".connect").toString())
+            .collect(Collectors.toList());
+    return TestUtils.runCompiler(grammarFile, connectFiles, rootNode,
+            getDirectoryName(), expectedReturnValue);
+  }
+
+  protected void testAndCompare(String inputDirectoryName, String expectedName, String rootNode, String... connectNames) throws IOException {
+    Path outPath = test(inputDirectoryName, 1, rootNode, connectNames);
+
+    final String startOfErrorsPattern = "Errors:\n";
+    String out = readFile(outPath, Charset.defaultCharset());
+    assertThat(out).contains(startOfErrorsPattern);
+    out = out.substring(out.indexOf(startOfErrorsPattern) + startOfErrorsPattern.length());
+
+    TestUtils.assertLinesMatch(getDirectoryName(), expectedName, out);
+
+    logger.info("ragconnect for " + expectedName + " returned:\n{}", out);
+  }
+}
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ErrorsTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ErrorsTest.java
index 4853b6a7e62f6e71878b37700a72fe2515655200..7f7e0cb2fbbaba2f6ce2995d9866ba10142e2039 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ErrorsTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/ErrorsTest.java
@@ -1,65 +1,33 @@
 package org.jastadd.ragconnect.tests;
 
-import org.jastadd.ragconnect.tests.utils.TestUtils;
-import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 
-import java.io.File;
 import java.io.IOException;
-import java.nio.charset.Charset;
-import java.nio.file.Path;
-import java.util.Arrays;
-import java.util.List;
-import java.util.stream.Collectors;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.jastadd.ragconnect.tests.utils.TestUtils.readFile;
-import static org.junit.jupiter.api.Assertions.assertTrue;
 
 /**
  * Test error messages.
  *
  * @author rschoene - Initial contribution
  */
-public class ErrorsTest extends RagConnectTest {
-
-  private static final String ERROR_DIRECTORY = "errors/";
-  private static final String OUTPUT_DIRECTORY = TestUtils.OUTPUT_DIRECTORY_PREFIX + ERROR_DIRECTORY;
+public class ErrorsTest extends AbstractCompilerTest {
 
-  private static final String DEFAULT_GRAMMAR_NAME = "Errors";
+  @Override
+  protected String getDirectoryName() {
+    return "errors";
+  }
 
-  @BeforeAll
-  public static void createOutputDirectory() {
-    File outputDirectory = new File(OUTPUT_DIRECTORY);
-    assertTrue((outputDirectory.exists() && outputDirectory.isDirectory()) || outputDirectory.mkdir());
+  @Override
+  protected String getDefaultGrammarName() {
+    return "Errors";
   }
 
   @Test
   void testStandardErrors() throws IOException {
-    test("Standard", "A", "Standard");
+    testAndCompare(getDirectoryName(), "Standard", "A", "Standard");
   }
 
   @Test
   void testTwoPartsErrors() throws IOException {
-    test("Part", "A", "Part1", "Part2");
+    testAndCompare(getDirectoryName(), "Part", "A", "Part1", "Part2");
   }
-
-  @SuppressWarnings("SameParameterValue")
-  private void test(String expectedName, String rootNode, String... connectNames) throws IOException {
-    String grammarFile = ERROR_DIRECTORY + DEFAULT_GRAMMAR_NAME + ".relast";
-    List<String> connectFiles = Arrays.stream(connectNames)
-        .map(connectName -> ERROR_DIRECTORY + connectName + ".connect")
-        .collect(Collectors.toList());
-    Path outPath = TestUtils.runCompiler(grammarFile, connectFiles, rootNode, ERROR_DIRECTORY, 1);
-
-    final String startOfErrorsPattern = "Errors:\n";
-    String out = readFile(outPath, Charset.defaultCharset());
-    assertThat(out).contains(startOfErrorsPattern);
-    out = out.substring(out.indexOf(startOfErrorsPattern) + startOfErrorsPattern.length());
-
-    TestUtils.assertLinesMatch(ERROR_DIRECTORY, expectedName, out);
-
-    logger.info("ragconnect for " + expectedName + " returned:\n{}", out);
-  }
-
 }
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/JavaTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/JavaTest.java
index 81d2817c5a1453bb82728edb496d1b41042d31ba..dfac385b2161cf4fff96aad5d05985438a03f242 100644
--- a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/JavaTest.java
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/JavaTest.java
@@ -150,7 +150,10 @@ public class JavaTest extends RagConnectTest {
 
     model.ragconnectJavaPush(TOPIC_RECEIVE_NTA, ExposingASTNode.INSTANCE.aToBytes(createA("12")));
     checker.put(TOPIC_RECEIVE_NTA, "12").check();
+  }
 
+  @AfterEach
+  public void printEvaluationSummary() {
     System.out.println(model.ragconnectEvaluationCounterSummary());
   }
 
diff --git a/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/PassingTest.java b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/PassingTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..5829c21d41f8ee0601a5218c33bb3d7165ae457b
--- /dev/null
+++ b/ragconnect.tests/src/test/java/org/jastadd/ragconnect/tests/PassingTest.java
@@ -0,0 +1,33 @@
+package org.jastadd.ragconnect.tests;
+
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.nio.file.Paths;
+
+/**
+ * Use cases that just need to compile.
+ *
+ * @author rschoene - Initial contribution
+ */
+public class PassingTest extends AbstractCompilerTest {
+  @Override
+  protected String getDirectoryName() {
+    return "passing";
+  }
+
+  @Override
+  protected String getDefaultGrammarName() {
+    return "Test";
+  }
+
+  protected void run(String inputDirectoryName, String rootNode) throws IOException {
+    super.test(Paths.get("passing", inputDirectoryName).toString(),
+            0, rootNode, getDefaultGrammarName());
+  }
+
+  @Test
+  public void testInheritance() throws IOException {
+    run("inheritance", "Root");
+  }
+}