From 6381b8dd6525bf708fd01b4e7aef4d09689b8687 Mon Sep 17 00:00:00 2001 From: trydofor Date: Thu, 25 Jan 2024 15:49:49 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=92=9A=20update=20github=20actions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/release.yml | 84 +++++++++++-------- pom.xml | 2 +- .../java/pro/fessional/meepo/TraceTest.java | 21 ++--- 3 files changed, 56 insertions(+), 51 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b358440..ac8b4c1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,15 +5,25 @@ on: workflow_dispatch: inputs: testCoverReport: - description: 'test and coveralls report?' + description: 'test and coverage report?' default: true type: boolean required: false testFailureIgnore: - description: 'maven.test.failure.ignore?' + description: 'test ignore failure?' default: false type: boolean required: false + testVerbose: + description: 'test output verbose' + default: false + type: boolean + required: false + deployOssrh: + description: 'deploy to ossrh?' + default: true + type: boolean + required: false release: types: [published] @@ -21,14 +31,20 @@ jobs: release: name: Release to Sonatype runs-on: ubuntu-latest + env: + MAVEN_OPTS: -Xmx2g + TEST_VERBOSE: ${{ inputs.testVerbose }} steps: - name: Checkout ${{github.event.release.tag_name}} uses: actions/checkout@v4 + with: + fetch-depth: 10 + ## chache asdf/, m2/repository - name: Cache Sdk & Repo id: cache-sdk-repo - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | ~/.asdf/ @@ -37,13 +53,32 @@ jobs: ## install jdk and maven - name: Install asdf & tools - uses: asdf-vm/actions/install@v2 + uses: asdf-vm/actions/install@v3 with: skip_install: ${{steps.cache-sdk-repo.outputs.cache-hit == 'true'}} ## write settings.xml - name: Maven settings.xml + id: settings run: | + JAVA_HOME=$(asdf where java) + echo "JAVA_HOME=$JAVA_HOME" >> "$GITHUB_OUTPUT" + echo "GIT_BRANCH=$(git branch --show-current)" >> "$GITHUB_OUTPUT" + + _opt=$(git describe --tags --exact-match 2>/dev/null || true) + if [ "$_opt" != "" ]; then + _opt="-Drevision=$_opt" + echo $_opt + echo "MVN_REVISION=$_opt" >> "$GITHUB_OUTPUT" + fi + + _ver=$(mvn --quiet --non-recursive -DforceStdout -Dexpression=project.version $_opt help:evaluate) + echo $_ver + echo "WINGS_VERSION=$_ver" >> "$GITHUB_OUTPUT" + + mvn -v + git --no-pager log --graph -10 --pretty=format:'%H - %ai %d %s' + mkdir -p ~/.m2 cat > ~/.m2/settings.xml << "EOF" @@ -57,56 +92,37 @@ jobs: EOF - ## get JAVA_HOME - - name: Env JAVA_HOME - id: java-env - run: echo "JAVA_HOME=$(asdf where java)" >> "$GITHUB_OUTPUT" - ## get revision if release - - name: Opt revision - id: opt-revision - if: github.event.release.tag_name != '' - run: echo "OPT_REVISION=-Drevision=${{github.event.release.tag_name}}" >> "$GITHUB_OUTPUT" - ## info deploy - - name: Info Deploy - run: | - mvn --quiet --non-recursive -DforceStdout -Dexpression=project.version ${{ steps.opt-revision.outputs.OPT_REVISION }} help:evaluate - echo - mvn -v - git --no-pager log --graph -10 --pretty=format:'%H - %an, %ad %d : %s' - env: - JAVA_HOME: ${{ steps.java-env.outputs.JAVA_HOME }} - ## ci report if not release - - name: Coveralls Report + + ## report if not release + - name: Test Coverage ${{ steps.settings.outputs.WINGS_VERSION }} ${{ steps.settings.outputs.GIT_BRANCH }} if: inputs.testCoverReport run: > mvn - --quiet - --fail-at-end - -P ci - -Dtest-verbose=false - -Dmaven.test.skip=false + -P coverage -Dmaven.test.failure.ignore=${{ inputs.testFailureIgnore }} -DrepoToken=${{ secrets.COVERALLS_REPO_TOKEN }} clean test jacoco:report coveralls:report env: - JAVA_HOME: ${{ steps.java-env.outputs.JAVA_HOME }} + JAVA_HOME: ${{ steps.settings.outputs.JAVA_HOME }} ## import gpp private key - name: Import GPG key + if: inputs.deployOssrh uses: crazy-max/ghaction-import-gpg@v6 with: gpg_private_key: ${{ secrets.MVN_GPG_SKEY }} passphrase: ${{ secrets.MVN_GPG_PASS }} + ## maven deploy - - name: Maven Deploy + - name: Deploy ${{ steps.settings.outputs.WINGS_VERSION }} ${{ steps.settings.outputs.GIT_BRANCH }} + if: inputs.deployOssrh run: > mvn -P ossrh - ${{ steps.opt-revision.outputs.OPT_REVISION }} + ${{ steps.settings.outputs.MVN_REVISION }} -Dgpg.passphrase=${MVN_GPG_PASS} - -Dmaven.test.skip=true clean deploy env: - JAVA_HOME: ${{ steps.java-env.outputs.JAVA_HOME }} + JAVA_HOME: ${{ steps.settings.outputs.JAVA_HOME }} MVN_OSS_USER: ${{ secrets.MVN_OSS_USER }} MVN_OSS_PASS: ${{ secrets.MVN_OSS_PASS }} MVN_GPG_PASS: ${{ secrets.MVN_GPG_PASS }} diff --git a/pom.xml b/pom.xml index a5f5538..550bef0 100644 --- a/pom.xml +++ b/pom.xml @@ -264,7 +264,7 @@ - ci + coverage false diff --git a/src/test/java/pro/fessional/meepo/TraceTest.java b/src/test/java/pro/fessional/meepo/TraceTest.java index 8b0022a..4f862d0 100644 --- a/src/test/java/pro/fessional/meepo/TraceTest.java +++ b/src/test/java/pro/fessional/meepo/TraceTest.java @@ -12,26 +12,15 @@ public class TraceTest { protected final Logger logger = LoggerFactory.getLogger(this.getClass()); - public static final String MeepoLogLevel = "meepo.log-level"; - public static final String TestVerbose = "test-verbose"; + public static final String TestVerbose = "TEST_VERBOSE"; - private static volatile boolean done = false; + private static volatile Boolean NotTestVerbose = null; @BeforeAll public static void setMeepoLogLevel() { - if (done) return; - - String lvl = System.getProperty(MeepoLogLevel); - if (lvl == null || lvl.isEmpty()) { - if ("false".equalsIgnoreCase(System.getProperty(TestVerbose))) { - lvl = "ERROR"; - } - else { - lvl = "TRACE"; - } + if (NotTestVerbose == null) { + NotTestVerbose = "false".equalsIgnoreCase(System.getenv(TestVerbose)); + System.setProperty("org.slf4j.simpleLogger.log.pro.fessional.meepo", NotTestVerbose ? "ERROR" : "DEBUG"); } - System.setProperty("org.slf4j.simpleLogger.log.pro.fessional.meepo", lvl); - - done = true; } } From 0fc99da54832b2c885f33c15cbc0adbc7e00c456 Mon Sep 17 00:00:00 2001 From: trydofor Date: Mon, 29 Jan 2024 12:28:02 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=94=A5=20remove=20hashCode,=20equals?= =?UTF-8?q?=20#3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 6 ++ .../java/pro/fessional/meepo/bind/Exon.java | 14 --- .../pro/fessional/meepo/bind/dna/DnaBkb.java | 14 --- .../pro/fessional/meepo/bind/dna/DnaEnd.java | 14 --- .../pro/fessional/meepo/bind/dna/DnaRaw.java | 14 --- .../pro/fessional/meepo/bind/dna/DnaSet.java | 16 ---- .../pro/fessional/meepo/bind/dna/DnaSon.java | 5 -- .../pro/fessional/meepo/bind/rna/RnaDone.java | 15 ---- .../pro/fessional/meepo/bind/rna/RnaEach.java | 19 ---- .../pro/fessional/meepo/bind/rna/RnaElse.java | 14 --- .../pro/fessional/meepo/bind/rna/RnaPut.java | 17 ---- .../pro/fessional/meepo/bind/rna/RnaRun.java | 18 ---- .../pro/fessional/meepo/bind/rna/RnaUse.java | 16 ---- .../pro/fessional/meepo/bind/rna/RnaWhen.java | 18 ---- .../pro/fessional/meepo/bind/txt/HiMeepo.java | 18 ---- .../fessional/meepo/bind/txt/TxtDnaSet.java | 14 --- .../fessional/meepo/bind/txt/TxtRnaRun.java | 16 ---- .../fessional/meepo/bind/txt/TxtRnaUse.java | 14 --- .../fessional/meepo/bind/txt/TxtSimple.java | 13 --- .../pro/fessional/meepo/bind/wow/Clop.java | 14 --- .../pro/fessional/meepo/bind/wow/Life.java | 55 +----------- .../pro/fessional/meepo/poof/RnaWarmed.java | 19 +--- .../java/pro/fessional/meepo/MeepoTest.java | 80 +++++++++++++++++ .../pro/fessional/meepo/TestingHelper.java | 66 ++++++++++++++ .../meepo/benchmark/ExpectedOutputTest.java | 22 ++--- .../meepo/benchmark/MeepoAsyncProfile.java | 3 +- .../meepo/benchmark/MeepoBenchmark.java | 3 +- .../meepo/benchmark/PebbleAsyncProfile.java | 3 +- .../meepo/benchmark/PebbleBenchmark.java | 3 +- .../pro/fessional/meepo/benchmark/Stock.java | 38 -------- .../fessional/meepo/bind/dna/DnaBkbTest.java | 11 --- .../fessional/meepo/bind/dna/DnaEndTest.java | 12 --- .../fessional/meepo/bind/dna/DnaRawTest.java | 10 --- .../fessional/meepo/bind/dna/DnaSetTest.java | 12 --- .../fessional/meepo/bind/rna/RnaPutTest.java | 12 --- .../fessional/meepo/bind/rna/RnaRunTest.java | 12 --- .../fessional/meepo/bind/rna/RnaUseTest.java | 12 --- .../fessional/meepo/bind/txt/HiMeepoTest.java | 12 --- .../meepo/bind/txt/TxtSimpleTest.java | 12 --- .../fessional/meepo/bind/wow/LifeTest.java | 14 +-- .../pro/fessional/meepo/sack/ParserTest.java | 14 ++- .../template/jmh/stocks.meepo-reverse.html | 87 +++++++++++++++++++ 42 files changed, 277 insertions(+), 524 deletions(-) create mode 100644 src/test/java/pro/fessional/meepo/MeepoTest.java create mode 100644 src/test/java/pro/fessional/meepo/TestingHelper.java create mode 100755 src/test/resources/template/jmh/stocks.meepo-reverse.html diff --git a/pom.xml b/pom.xml index 550bef0..8d5c21e 100644 --- a/pom.xml +++ b/pom.xml @@ -279,6 +279,12 @@ org.jacoco jacoco-maven-plugin 0.8.11 + + + + **/WhoAmI.* + + prepare-agent diff --git a/src/main/java/pro/fessional/meepo/bind/Exon.java b/src/main/java/pro/fessional/meepo/bind/Exon.java index 56c147b..485daf7 100644 --- a/src/main/java/pro/fessional/meepo/bind/Exon.java +++ b/src/main/java/pro/fessional/meepo/bind/Exon.java @@ -10,7 +10,6 @@ import java.io.Writer; import java.util.Collections; import java.util.List; -import java.util.Objects; /** *
@@ -114,18 +113,5 @@ public boolean cross(N o) {
         public int compareTo(@NotNull Exon.N o) {
             return start == o.start ? o.until - until : start - o.start;
         }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(start, until, xna);
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) return true;
-            if (!(o instanceof N)) return false;
-            N n = (N) o;
-            return start == n.start && until == n.until && Objects.equals(xna, n.xna);
-        }
     }
 }
diff --git a/src/main/java/pro/fessional/meepo/bind/dna/DnaBkb.java b/src/main/java/pro/fessional/meepo/bind/dna/DnaBkb.java
index c5dca3c..f387c21 100644
--- a/src/main/java/pro/fessional/meepo/bind/dna/DnaBkb.java
+++ b/src/main/java/pro/fessional/meepo/bind/dna/DnaBkb.java
@@ -9,7 +9,6 @@
 import java.io.IOException;
 import java.io.StringWriter;
 import java.io.Writer;
-import java.util.Objects;
 
 /**
  * 
@@ -38,19 +37,6 @@ public DnaBkb(String text, Clop edge, @NotNull String name) {
         this.name9 = name.toCharArray();
     }
 
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        DnaBkb dnaBkb = (DnaBkb) o;
-        return name.equals(dnaBkb.name);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(name);
-    }
-
     @Override
     public String toString() {
         StringWriter buff = new StringWriter();
diff --git a/src/main/java/pro/fessional/meepo/bind/dna/DnaEnd.java b/src/main/java/pro/fessional/meepo/bind/dna/DnaEnd.java
index 51db1ac..b53fabd 100644
--- a/src/main/java/pro/fessional/meepo/bind/dna/DnaEnd.java
+++ b/src/main/java/pro/fessional/meepo/bind/dna/DnaEnd.java
@@ -11,7 +11,6 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
-import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -45,19 +44,6 @@ public DnaEnd(String text, Clop edge, Collection name) {
         }
     }
 
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        DnaEnd dnaEnd = (DnaEnd) o;
-        return name.equals(dnaEnd.name);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(name);
-    }
-
     @Override
     public String toString() {
         StringWriter buff = new StringWriter();
diff --git a/src/main/java/pro/fessional/meepo/bind/dna/DnaRaw.java b/src/main/java/pro/fessional/meepo/bind/dna/DnaRaw.java
index af9e472..51129c4 100644
--- a/src/main/java/pro/fessional/meepo/bind/dna/DnaRaw.java
+++ b/src/main/java/pro/fessional/meepo/bind/dna/DnaRaw.java
@@ -8,7 +8,6 @@
 import java.io.IOException;
 import java.io.StringWriter;
 import java.io.Writer;
-import java.util.Arrays;
 
 /**
  * 
@@ -38,19 +37,6 @@ public void merge(Acid acid, Writer buff) {
         Dent.write(buff, body9);
     }
 
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        DnaRaw dnaRaw = (DnaRaw) o;
-        return Arrays.equals(body9, dnaRaw.body9);
-    }
-
-    @Override
-    public int hashCode() {
-        return Arrays.hashCode(body9);
-    }
-
     @Override
     public String toString() {
         StringWriter buff = new StringWriter();
diff --git a/src/main/java/pro/fessional/meepo/bind/dna/DnaSet.java b/src/main/java/pro/fessional/meepo/bind/dna/DnaSet.java
index ad972a0..7dccb61 100644
--- a/src/main/java/pro/fessional/meepo/bind/dna/DnaSet.java
+++ b/src/main/java/pro/fessional/meepo/bind/dna/DnaSet.java
@@ -14,7 +14,6 @@
 import java.io.Writer;
 import java.util.Collections;
 import java.util.List;
-import java.util.Objects;
 import java.util.regex.Pattern;
 
 /**
@@ -62,21 +61,6 @@ public Life.State match(List lst, String txt) {
         }
     }
 
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        DnaSet dnaSet = (DnaSet) o;
-        return Objects.equals(find.pattern(), dnaSet.find.pattern()) &&
-               Objects.equals(repl, dnaSet.repl) &&
-               Objects.equals(life, dnaSet.life);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(find.pattern(), repl, life);
-    }
-
     @Override
     public String toString() {
         StringWriter buff = new StringWriter();
diff --git a/src/main/java/pro/fessional/meepo/bind/dna/DnaSon.java b/src/main/java/pro/fessional/meepo/bind/dna/DnaSon.java
index 869f27a..76fc618 100644
--- a/src/main/java/pro/fessional/meepo/bind/dna/DnaSon.java
+++ b/src/main/java/pro/fessional/meepo/bind/dna/DnaSon.java
@@ -46,11 +46,6 @@ public void merge(Acid acid, Writer buff) {
         }
     }
 
-    @Override
-    public int hashCode() {
-        return path.hashCode();
-    }
-
     @Override
     public String toString() {
         StringWriter buff = new StringWriter();
diff --git a/src/main/java/pro/fessional/meepo/bind/rna/RnaDone.java b/src/main/java/pro/fessional/meepo/bind/rna/RnaDone.java
index 182ef45..a53f606 100644
--- a/src/main/java/pro/fessional/meepo/bind/rna/RnaDone.java
+++ b/src/main/java/pro/fessional/meepo/bind/rna/RnaDone.java
@@ -13,7 +13,6 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
-import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -59,20 +58,6 @@ public void merge(Acid acid, Writer buff) {
         }
     }
 
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        RnaDone rnaDone = (RnaDone) o;
-        return name.equals(rnaDone.name) &&
-               tock.equals(rnaDone.tock);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(name, tock);
-    }
-
     @Override
     public String toString() {
         StringWriter buff = new StringWriter();
diff --git a/src/main/java/pro/fessional/meepo/bind/rna/RnaEach.java b/src/main/java/pro/fessional/meepo/bind/rna/RnaEach.java
index c9a1820..88b92f6 100644
--- a/src/main/java/pro/fessional/meepo/bind/rna/RnaEach.java
+++ b/src/main/java/pro/fessional/meepo/bind/rna/RnaEach.java
@@ -20,7 +20,6 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 import java.util.RandomAccess;
 
 import static pro.fessional.meepo.bind.Const.BLT$EACH_COUNT;
@@ -205,24 +204,6 @@ private void loop(Acid acid, Writer buf, Object obj, int size,
         }
     }
 
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        RnaEach rnaEach = (RnaEach) o;
-        return mute == rnaEach.mute &&
-               step == rnaEach.step &&
-               tock.equals(rnaEach.tock) &&
-               type.equals(rnaEach.type) &&
-               expr.equals(rnaEach.expr);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(tock, type, step, expr, mute);
-    }
-
     @Override
     public String toString() {
         StringWriter buff = new StringWriter();
diff --git a/src/main/java/pro/fessional/meepo/bind/rna/RnaElse.java b/src/main/java/pro/fessional/meepo/bind/rna/RnaElse.java
index e8ebbd4..d745c4d 100644
--- a/src/main/java/pro/fessional/meepo/bind/rna/RnaElse.java
+++ b/src/main/java/pro/fessional/meepo/bind/rna/RnaElse.java
@@ -10,7 +10,6 @@
 import java.io.IOException;
 import java.io.StringWriter;
 import java.io.Writer;
-import java.util.Objects;
 
 /**
  * 
@@ -40,19 +39,6 @@ public void merge(Acid acid, Writer buff) {
         }
     }
 
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        RnaElse tock1 = (RnaElse) o;
-        return tock.equals(tock1.tock);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(tock);
-    }
-
     @Override
     public String toString() {
         StringWriter buff = new StringWriter();
diff --git a/src/main/java/pro/fessional/meepo/bind/rna/RnaPut.java b/src/main/java/pro/fessional/meepo/bind/rna/RnaPut.java
index 89145f3..92dc021 100644
--- a/src/main/java/pro/fessional/meepo/bind/rna/RnaPut.java
+++ b/src/main/java/pro/fessional/meepo/bind/rna/RnaPut.java
@@ -14,7 +14,6 @@
 import java.io.StringWriter;
 import java.io.Writer;
 import java.util.Map;
-import java.util.Objects;
 
 /**
  * 
@@ -67,22 +66,6 @@ public void merge(Acid acid, Writer buff) {
         ctx.put(para, obj);
     }
 
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        RnaPut rnaPut = (RnaPut) o;
-        return type.equals(rnaPut.type) &&
-               para.equals(rnaPut.para) &&
-               expr.equals(rnaPut.expr) &&
-               mute == rnaPut.mute;
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(type, para, expr, mute);
-    }
-
     @Override
     public String toString() {
         StringWriter buff = new StringWriter();
diff --git a/src/main/java/pro/fessional/meepo/bind/rna/RnaRun.java b/src/main/java/pro/fessional/meepo/bind/rna/RnaRun.java
index c9be49e..a2e9403 100644
--- a/src/main/java/pro/fessional/meepo/bind/rna/RnaRun.java
+++ b/src/main/java/pro/fessional/meepo/bind/rna/RnaRun.java
@@ -15,7 +15,6 @@
 import java.io.Writer;
 import java.util.Collections;
 import java.util.List;
-import java.util.Objects;
 import java.util.regex.Pattern;
 
 /**
@@ -64,23 +63,6 @@ public Life.State match(List lst, String txt) {
         return Collections.singletonList(new TxtRnaRun(txt, pos, type, expr, mute, bar));
     }
 
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        RnaRun rnaRun = (RnaRun) o;
-        return type.equals(rnaRun.type) &&
-               find.pattern().equals(rnaRun.find.pattern()) &&
-               expr.equals(rnaRun.expr) &&
-               mute == rnaRun.mute &&
-               life.equals(rnaRun.life);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(type, find.pattern(), expr, mute, life);
-    }
-
     @Override
     public String toString() {
         StringWriter buff = new StringWriter();
diff --git a/src/main/java/pro/fessional/meepo/bind/rna/RnaUse.java b/src/main/java/pro/fessional/meepo/bind/rna/RnaUse.java
index 08ca2a1..e41f2f6 100644
--- a/src/main/java/pro/fessional/meepo/bind/rna/RnaUse.java
+++ b/src/main/java/pro/fessional/meepo/bind/rna/RnaUse.java
@@ -15,7 +15,6 @@
 import java.io.Writer;
 import java.util.Collections;
 import java.util.List;
-import java.util.Objects;
 import java.util.regex.Pattern;
 
 /**
@@ -58,21 +57,6 @@ public Life.State match(List lst, String txt) {
         return Collections.singletonList(new TxtRnaUse(txt, pos, para, bar));
     }
 
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        RnaUse rnaUse = (RnaUse) o;
-        return find.pattern().equals(rnaUse.find.pattern()) &&
-               para.equals(rnaUse.para) &&
-               life.equals(rnaUse.life);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(find.pattern(), para, life);
-    }
-
     @Override
     public String toString() {
         StringWriter buff = new StringWriter();
diff --git a/src/main/java/pro/fessional/meepo/bind/rna/RnaWhen.java b/src/main/java/pro/fessional/meepo/bind/rna/RnaWhen.java
index 274553f..efa400e 100644
--- a/src/main/java/pro/fessional/meepo/bind/rna/RnaWhen.java
+++ b/src/main/java/pro/fessional/meepo/bind/rna/RnaWhen.java
@@ -15,7 +15,6 @@
 import java.io.IOException;
 import java.io.StringWriter;
 import java.io.Writer;
-import java.util.Objects;
 
 /**
  * 
@@ -87,23 +86,6 @@ public void merge(Acid acid, Writer buff) {
         }
     }
 
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        RnaWhen rnaWhen = (RnaWhen) o;
-        return nope == rnaWhen.nope &&
-               mute == rnaWhen.mute &&
-               tock.equals(rnaWhen.tock) &&
-               type.equals(rnaWhen.type) &&
-               expr.equals(rnaWhen.expr);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(tock, type, nope, expr, mute);
-    }
-
     @Override
     public String toString() {
         StringWriter buff = new StringWriter();
diff --git a/src/main/java/pro/fessional/meepo/bind/txt/HiMeepo.java b/src/main/java/pro/fessional/meepo/bind/txt/HiMeepo.java
index 72e6842..a240a36 100644
--- a/src/main/java/pro/fessional/meepo/bind/txt/HiMeepo.java
+++ b/src/main/java/pro/fessional/meepo/bind/txt/HiMeepo.java
@@ -8,7 +8,6 @@
 import java.io.IOException;
 import java.io.StringWriter;
 import java.io.Writer;
-import java.util.Objects;
 
 /**
  * 
@@ -64,23 +63,6 @@ public HiMeepo(@NotNull String text, Clop edge, @NotNull String head, @NotNull S
         this.crlf = "\n".equals(tail);
     }
 
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        HiMeepo hiMeepo = (HiMeepo) o;
-        return echo == hiMeepo.echo &&
-               trim == hiMeepo.trim &&
-               crlf == hiMeepo.crlf &&
-               head.equals(hiMeepo.head) &&
-               tail.equals(hiMeepo.tail);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(head, tail, echo, trim, crlf);
-    }
-
     @Override
     public String toString() {
         StringWriter buff = new StringWriter();
diff --git a/src/main/java/pro/fessional/meepo/bind/txt/TxtDnaSet.java b/src/main/java/pro/fessional/meepo/bind/txt/TxtDnaSet.java
index b2444e6..41649ca 100644
--- a/src/main/java/pro/fessional/meepo/bind/txt/TxtDnaSet.java
+++ b/src/main/java/pro/fessional/meepo/bind/txt/TxtDnaSet.java
@@ -9,7 +9,6 @@
 import java.io.IOException;
 import java.io.StringWriter;
 import java.io.Writer;
-import java.util.Arrays;
 
 /**
  * Static replacement
@@ -32,19 +31,6 @@ public void merge(Acid acid, Writer buff) {
         Dent.write(buff, repl9);
     }
 
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        TxtDnaSet txtDnaSet = (TxtDnaSet) o;
-        return Arrays.equals(repl9, txtDnaSet.repl9);
-    }
-
-    @Override
-    public int hashCode() {
-        return Arrays.hashCode(repl9);
-    }
-
     @Override
     public String toString() {
         StringWriter buff = new StringWriter();
diff --git a/src/main/java/pro/fessional/meepo/bind/txt/TxtRnaRun.java b/src/main/java/pro/fessional/meepo/bind/txt/TxtRnaRun.java
index d281740..e1d1957 100644
--- a/src/main/java/pro/fessional/meepo/bind/txt/TxtRnaRun.java
+++ b/src/main/java/pro/fessional/meepo/bind/txt/TxtRnaRun.java
@@ -13,7 +13,6 @@
 import java.io.IOException;
 import java.io.StringWriter;
 import java.io.Writer;
-import java.util.Objects;
 
 /**
  * Get Var from the Execution Engine
@@ -55,21 +54,6 @@ public void merge(Acid acid, Writer buff) {
         }
     }
 
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        TxtRnaRun txtRnaRun = (TxtRnaRun) o;
-        return mute == txtRnaRun.mute &&
-               type.equals(txtRnaRun.type) &&
-               expr.equals(txtRnaRun.expr);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(type, expr, mute);
-    }
-
     @Override
     public String toString() {
         StringWriter buff = new StringWriter();
diff --git a/src/main/java/pro/fessional/meepo/bind/txt/TxtRnaUse.java b/src/main/java/pro/fessional/meepo/bind/txt/TxtRnaUse.java
index 07e4f35..d14f150 100644
--- a/src/main/java/pro/fessional/meepo/bind/txt/TxtRnaUse.java
+++ b/src/main/java/pro/fessional/meepo/bind/txt/TxtRnaUse.java
@@ -13,7 +13,6 @@
 import java.io.IOException;
 import java.io.StringWriter;
 import java.io.Writer;
-import java.util.Objects;
 
 import static pro.fessional.meepo.bind.Const.ENGINE$MAP;
 
@@ -51,19 +50,6 @@ public void merge(Acid acid, Writer buff) {
         Dent.indent(buff, left, o);
     }
 
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        TxtRnaUse txtRnaUse = (TxtRnaUse) o;
-        return expr.equals(txtRnaUse.expr);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(expr);
-    }
-
     @Override
     public String toString() {
         StringWriter buff = new StringWriter();
diff --git a/src/main/java/pro/fessional/meepo/bind/txt/TxtSimple.java b/src/main/java/pro/fessional/meepo/bind/txt/TxtSimple.java
index 44962cd..0bb024a 100644
--- a/src/main/java/pro/fessional/meepo/bind/txt/TxtSimple.java
+++ b/src/main/java/pro/fessional/meepo/bind/txt/TxtSimple.java
@@ -9,7 +9,6 @@
 import java.io.IOException;
 import java.io.StringWriter;
 import java.io.Writer;
-import java.util.Arrays;
 
 /**
  * 
@@ -32,18 +31,6 @@ public void merge(Acid acid, Writer buff) {
         Dent.write(buff, text9);
     }
 
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        TxtSimple txt = (TxtSimple) o;
-        return Arrays.equals(text9, txt.text9);
-    }
-
-    @Override
-    public int hashCode() {
-        return Arrays.hashCode(text9);
-    }
 
     @Override
     public String toString() {
diff --git a/src/main/java/pro/fessional/meepo/bind/wow/Clop.java b/src/main/java/pro/fessional/meepo/bind/wow/Clop.java
index 51e9503..10a4b8b 100644
--- a/src/main/java/pro/fessional/meepo/bind/wow/Clop.java
+++ b/src/main/java/pro/fessional/meepo/bind/wow/Clop.java
@@ -5,7 +5,6 @@
 import java.io.IOException;
 import java.io.StringWriter;
 import java.io.Writer;
-import java.util.Objects;
 
 /**
  * close-open range
@@ -39,19 +38,6 @@ public boolean cross(Clop o) {
         return until > o.start && o.until > start;
     }
 
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        Clop n = (Clop) o;
-        return start == n.start && until == n.until;
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(start, until);
-    }
-
     @Override
     public int compareTo(@NotNull Clop o) {
         return start == o.start ? o.until - until : start - o.start;
diff --git a/src/main/java/pro/fessional/meepo/bind/wow/Life.java b/src/main/java/pro/fessional/meepo/bind/wow/Life.java
index 8c393b3..8059f03 100644
--- a/src/main/java/pro/fessional/meepo/bind/wow/Life.java
+++ b/src/main/java/pro/fessional/meepo/bind/wow/Life.java
@@ -11,7 +11,6 @@
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
-import java.util.Objects;
 
 /**
  * @author trydofor
@@ -34,10 +33,10 @@ public enum State {
         Live
     }
 
-    private int count = 0;
-    private int index = 0;
+    protected int count = 0;
+    protected int index = 0;
     @NotNull
-    private final List book; // null = any
+    protected final List book; // null = any
     @NotNull
     public final String name;
 
@@ -94,54 +93,6 @@ public void reset() {
         index = 0;
     }
 
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        Life life = (Life) o;
-        if (count == life.count &&
-            index == life.index &&
-            book.size() == life.book.size() &&
-            Objects.equals(name, life.name)) {
-            Iterator it0 = book.iterator();
-            Iterator it1 = life.book.iterator();
-            while (it0.hasNext()) {
-                int[] i0 = it0.next();
-                int[] i2 = it1.next();
-                if (i0.length != i2.length) return false;
-                for (int i = 0; i < i0.length; i++) {
-                    if (i0[i] != i2[i]) {
-                        return false;
-                    }
-                }
-            }
-            return true;
-        }
-        else {
-            return false;
-        }
-    }
-
-    @Override
-    public int hashCode() {
-        int[] objs = new int[2 + book.size() * 2];
-        int off = 0;
-        objs[off++] = count;
-        objs[off++] = index;
-        for (int[] arr : book) {
-            if (arr.length == 1) {
-                objs[off++] = arr[0];
-                objs[off++] = arr[0];
-            }
-            else {
-                objs[off++] = arr[0];
-                objs[off++] = arr[1];
-            }
-        }
-
-        return Arrays.hashCode(objs) + 31 * name.hashCode();
-    }
-
     @Override
     public String toString() {
         StringWriter buff = new StringWriter();
diff --git a/src/main/java/pro/fessional/meepo/poof/RnaWarmed.java b/src/main/java/pro/fessional/meepo/poof/RnaWarmed.java
index 71ab704..a64dbe0 100644
--- a/src/main/java/pro/fessional/meepo/poof/RnaWarmed.java
+++ b/src/main/java/pro/fessional/meepo/poof/RnaWarmed.java
@@ -2,8 +2,6 @@
 
 import org.jetbrains.annotations.NotNull;
 
-import java.util.Objects;
-
 import static pro.fessional.meepo.bind.Const.TXT$EMPTY;
 
 /**
@@ -56,22 +54,7 @@ public  T getTypedWork() {
     }
 
     public boolean hasInfo() {
-        return info != null && info.length() > 0;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        RnaWarmed warmed = (RnaWarmed) o;
-        return kind == warmed.kind &&
-               type.equals(warmed.type) &&
-               expr.equals(warmed.expr);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(type, expr, kind);
+        return info != null && !info.isEmpty();
     }
 
     @Override
diff --git a/src/test/java/pro/fessional/meepo/MeepoTest.java b/src/test/java/pro/fessional/meepo/MeepoTest.java
new file mode 100644
index 0000000..5841121
--- /dev/null
+++ b/src/test/java/pro/fessional/meepo/MeepoTest.java
@@ -0,0 +1,80 @@
+package pro.fessional.meepo;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import pro.fessional.meepo.benchmark.Stock;
+import pro.fessional.meepo.bind.Exon;
+import pro.fessional.meepo.bind.wow.Tock;
+import pro.fessional.meepo.sack.Gene;
+
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author trydofor
+ * @since 2024-01-29
+ */
+public class MeepoTest extends TraceTest {
+
+    @Test
+    void meepoNormal() {
+        final Gene meepo = Meepo.parse(new File("src/test/resources/template/jmh/stocks.meepo.html"));
+        final ArrayList items = TestingHelper.mockStocks();
+
+        LinkedList lnk = new LinkedList<>(items);
+        Map ctx1 = TestingHelper.mockContext(lnk);
+        String out1 = TestingHelper.trimOutput(meepo.merge(ctx1));
+        Assertions.assertEquals(TestingHelper.ExpectOutputContent, out1);
+
+        Stock[] arr = items.toArray(new Stock[0]);
+        Map ctx2 = TestingHelper.mockContext(arr);
+        String out2 = TestingHelper.trimOutput(meepo.merge(ctx2));
+        Assertions.assertEquals(TestingHelper.ExpectOutputContent, out2);
+    }
+
+    @Test
+    void meepoReverse() throws Exception {
+        final Gene meepo = Meepo.parse("meepoReverse", Files.newInputStream(Paths.get("src/test/resources/template/jmh/stocks.meepo-reverse.html")));
+        final ArrayList items = TestingHelper.mockStocks();
+        Collections.reverse(items);
+
+        LinkedList lnk = new LinkedList<>(items);
+        Map ctx1 = TestingHelper.mockContext(lnk);
+        String out1 = TestingHelper.trimOutput(meepo.merge(ctx1));
+        Assertions.assertEquals(TestingHelper.ExpectOutputContent, out1);
+
+        Stock[] arr = items.toArray(new Stock[0]);
+        Map ctx2 = TestingHelper.mockContext(arr);
+        String out2 = TestingHelper.trimOutput(meepo.merge(ctx2));
+        Assertions.assertEquals(TestingHelper.ExpectOutputContent, out2);
+    }
+
+    @Test
+    void testToString() {
+        final Gene meepo = Meepo.parse(new File("src/test/resources/template/jmh/stocks.meepo.html"));
+        LinkedHashSet exons = new LinkedHashSet<>();
+        traval(exons, meepo.exon);
+        for (Exon ex : exons) {
+            System.out.println(ex);
+        }
+    }
+
+    void traval(LinkedHashSet exon, List list) {
+        for (Exon ex : list) {
+            if (ex instanceof Tock) {
+                traval(exon, ((Tock) ex).gene);
+            }
+            else {
+                Assertions.assertFalse(exon.contains(ex), ex.toString());
+                exon.add(ex);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/pro/fessional/meepo/TestingHelper.java b/src/test/java/pro/fessional/meepo/TestingHelper.java
new file mode 100644
index 0000000..93bcf89
--- /dev/null
+++ b/src/test/java/pro/fessional/meepo/TestingHelper.java
@@ -0,0 +1,66 @@
+package pro.fessional.meepo;
+
+import pro.fessional.meepo.benchmark.Stock;
+import pro.fessional.meepo.util.Read;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author trydofor
+ * @since 2024-01-29
+ */
+public class TestingHelper {
+
+    public static final String ExpectOutputClasspath = "classpath:/template/jmh/expected-output.html";
+    public static final String ExpectOutputContent = trimOutput(Read.read(ExpectOutputClasspath));
+
+    public static ArrayList mockStocks() {
+        ArrayList items = new ArrayList<>();
+        items.add(new Stock("Adobe Systems", "Adobe Systems Inc.", "http://www.adobe.com", "ADBE", 39.26, 0.13, 0.33));
+        items.add(new Stock("Advanced Micro Devices", "Advanced Micro Devices Inc.", "http://www.amd.com", "AMD",
+                16.22, 0.17, 1.06));
+        items.add(new Stock("Amazon.com", "Amazon.com Inc", "http://www.amazon.com", "AMZN", 36.85, -0.23, -0.62));
+        items.add(new Stock("Apple", "Apple Inc.", "http://www.apple.com", "AAPL", 85.38, -0.87, -1.01));
+        items.add(new Stock("BEA Systems", "BEA Systems Inc.", "http://www.bea.com", "BEAS", 12.46, 0.09, 0.73));
+        items.add(new Stock("CA", "CA, Inc.", "http://www.ca.com", "CA", 24.66, 0.38, 1.57));
+        items.add(new Stock("Cisco Systems", "Cisco Systems Inc.", "http://www.cisco.com", "CSCO", 26.35, 0.13, 0.5));
+        items.add(new Stock("Dell", "Dell Corp.", "http://www.dell.com/", "DELL", 23.73, -0.42, -1.74));
+        items.add(new Stock("eBay", "eBay Inc.", "http://www.ebay.com", "EBAY", 31.65, -0.8, -2.47));
+        items.add(new Stock("Google", "Google Inc.", "http://www.google.com", "GOOG", 495.84, 7.75, 1.59));
+        items.add(new Stock("Hewlett-Packard", "Hewlett-Packard Co.", "http://www.hp.com", "HPQ", 41.69, -0.02, -0.05));
+        items.add(new Stock("IBM", "International Business Machines Corp.", "http://www.ibm.com", "IBM", 97.45, -0.06,
+                -0.06));
+        items.add(new Stock("Intel", "Intel Corp.", "http://www.intel.com", "INTC", 20.53, -0.07, -0.34));
+        items.add(new Stock("Juniper Networks", "Juniper Networks, Inc", "http://www.juniper.net/", "JNPR", 18.96, 0.5,
+                2.71));
+        items.add(new Stock("Microsoft", "Microsoft Corp", "http://www.microsoft.com", "MSFT", 30.6, 0.15, 0.49));
+        items.add(new Stock("Oracle", "Oracle Corp.", "http://www.oracle.com", "ORCL", 17.15, 0.17, 1.1));
+        items.add(new Stock("SAP", "SAP AG", "http://www.sap.com", "SAP", 46.2, -0.16, -0.35));
+        items.add(new Stock("Seagate Technology", "Seagate Technology", "http://www.seagate.com/", "STX", 27.35, -0.36,
+                -1.3));
+        items.add(new Stock("Sun Microsystems", "Sun Microsystems Inc.", "http://www.sun.com", "SUNW", 6.33, -0.01,
+                -0.16));
+        items.add(new Stock("Yahoo", "Yahoo! Inc.", "http://www.yahoo.com", "YHOO", 28.04, -0.17, -0.6));
+        return items;
+    }
+
+    public static Map mockContext(Object items) {
+        Map context = new HashMap<>();
+        context.put("items", items);
+        return context;
+    }
+
+    public static Map mockContext() {
+        return mockContext(mockStocks());
+    }
+
+    public static String trimOutput(String str) {
+        return str
+                .replaceAll("\\s", "")
+                .replaceAll(" context;
 
     public void setup() {
-        context = Stock.mockContext();
+        context = TestingHelper.mockContext();
         template = pro.fessional.meepo.Meepo.parse("classpath:/template/jmh/stocks.meepo.html");
     }
 
diff --git a/src/test/java/pro/fessional/meepo/benchmark/MeepoBenchmark.java b/src/test/java/pro/fessional/meepo/benchmark/MeepoBenchmark.java
index 71f8326..0e0c261 100644
--- a/src/test/java/pro/fessional/meepo/benchmark/MeepoBenchmark.java
+++ b/src/test/java/pro/fessional/meepo/benchmark/MeepoBenchmark.java
@@ -15,6 +15,7 @@
 import org.openjdk.jmh.runner.RunnerException;
 import org.openjdk.jmh.runner.options.Options;
 import org.openjdk.jmh.runner.options.OptionsBuilder;
+import pro.fessional.meepo.TestingHelper;
 import pro.fessional.meepo.sack.Gene;
 
 import java.io.IOException;
@@ -40,7 +41,7 @@ public class MeepoBenchmark {
 
     @Setup
     public void setup() throws PebbleException {
-        context = Stock.mockContext();
+        context = TestingHelper.mockContext();
         template = pro.fessional.meepo.Meepo.parse("classpath:/template/jmh/stocks.meepo.html");
     }
 
diff --git a/src/test/java/pro/fessional/meepo/benchmark/PebbleAsyncProfile.java b/src/test/java/pro/fessional/meepo/benchmark/PebbleAsyncProfile.java
index f9622f0..e7d7694 100644
--- a/src/test/java/pro/fessional/meepo/benchmark/PebbleAsyncProfile.java
+++ b/src/test/java/pro/fessional/meepo/benchmark/PebbleAsyncProfile.java
@@ -3,6 +3,7 @@
 import io.pebbletemplates.pebble.PebbleEngine;
 import io.pebbletemplates.pebble.error.PebbleException;
 import io.pebbletemplates.pebble.template.PebbleTemplate;
+import pro.fessional.meepo.TestingHelper;
 
 import java.io.IOException;
 import java.io.StringWriter;
@@ -20,7 +21,7 @@ public class PebbleAsyncProfile {
     private Map context;
 
     public void setup() throws PebbleException {
-        context = Stock.mockContext();
+        context = TestingHelper.mockContext();
         PebbleEngine engine = new PebbleEngine.Builder()
                 .autoEscaping(false).build();
         template = engine.getTemplate("template/jmh/stocks.pebble.html");
diff --git a/src/test/java/pro/fessional/meepo/benchmark/PebbleBenchmark.java b/src/test/java/pro/fessional/meepo/benchmark/PebbleBenchmark.java
index e42ef31..8a2196e 100644
--- a/src/test/java/pro/fessional/meepo/benchmark/PebbleBenchmark.java
+++ b/src/test/java/pro/fessional/meepo/benchmark/PebbleBenchmark.java
@@ -17,6 +17,7 @@
 import org.openjdk.jmh.runner.RunnerException;
 import org.openjdk.jmh.runner.options.Options;
 import org.openjdk.jmh.runner.options.OptionsBuilder;
+import pro.fessional.meepo.TestingHelper;
 
 import java.io.IOException;
 import java.io.StringWriter;
@@ -42,7 +43,7 @@ public class PebbleBenchmark {
 
     @Setup
     public void setup() throws PebbleException {
-        context = Stock.mockContext();
+        context = TestingHelper.mockContext();
         PebbleEngine engine = new PebbleEngine.Builder()
                 .autoEscaping(false).build();
         template = engine.getTemplate("template/jmh/stocks.pebble.html");
diff --git a/src/test/java/pro/fessional/meepo/benchmark/Stock.java b/src/test/java/pro/fessional/meepo/benchmark/Stock.java
index 1db1338..8684ed4 100644
--- a/src/test/java/pro/fessional/meepo/benchmark/Stock.java
+++ b/src/test/java/pro/fessional/meepo/benchmark/Stock.java
@@ -1,10 +1,5 @@
 package pro.fessional.meepo.benchmark;
 
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
 /**
  * @author trydofor
  * @since 2020-11-09
@@ -57,37 +52,4 @@ public double getRatio() {
         return this.ratio;
     }
 
-
-    public static Map mockContext() {
-        List items = new ArrayList<>();
-        items.add(new Stock("Adobe Systems", "Adobe Systems Inc.", "http://www.adobe.com", "ADBE", 39.26, 0.13, 0.33));
-        items.add(new Stock("Advanced Micro Devices", "Advanced Micro Devices Inc.", "http://www.amd.com", "AMD",
-                16.22, 0.17, 1.06));
-        items.add(new Stock("Amazon.com", "Amazon.com Inc", "http://www.amazon.com", "AMZN", 36.85, -0.23, -0.62));
-        items.add(new Stock("Apple", "Apple Inc.", "http://www.apple.com", "AAPL", 85.38, -0.87, -1.01));
-        items.add(new Stock("BEA Systems", "BEA Systems Inc.", "http://www.bea.com", "BEAS", 12.46, 0.09, 0.73));
-        items.add(new Stock("CA", "CA, Inc.", "http://www.ca.com", "CA", 24.66, 0.38, 1.57));
-        items.add(new Stock("Cisco Systems", "Cisco Systems Inc.", "http://www.cisco.com", "CSCO", 26.35, 0.13, 0.5));
-        items.add(new Stock("Dell", "Dell Corp.", "http://www.dell.com/", "DELL", 23.73, -0.42, -1.74));
-        items.add(new Stock("eBay", "eBay Inc.", "http://www.ebay.com", "EBAY", 31.65, -0.8, -2.47));
-        items.add(new Stock("Google", "Google Inc.", "http://www.google.com", "GOOG", 495.84, 7.75, 1.59));
-        items.add(new Stock("Hewlett-Packard", "Hewlett-Packard Co.", "http://www.hp.com", "HPQ", 41.69, -0.02, -0.05));
-        items.add(new Stock("IBM", "International Business Machines Corp.", "http://www.ibm.com", "IBM", 97.45, -0.06,
-                -0.06));
-        items.add(new Stock("Intel", "Intel Corp.", "http://www.intel.com", "INTC", 20.53, -0.07, -0.34));
-        items.add(new Stock("Juniper Networks", "Juniper Networks, Inc", "http://www.juniper.net/", "JNPR", 18.96, 0.5,
-                2.71));
-        items.add(new Stock("Microsoft", "Microsoft Corp", "http://www.microsoft.com", "MSFT", 30.6, 0.15, 0.49));
-        items.add(new Stock("Oracle", "Oracle Corp.", "http://www.oracle.com", "ORCL", 17.15, 0.17, 1.1));
-        items.add(new Stock("SAP", "SAP AG", "http://www.sap.com", "SAP", 46.2, -0.16, -0.35));
-        items.add(new Stock("Seagate Technology", "Seagate Technology", "http://www.seagate.com/", "STX", 27.35, -0.36,
-                -1.3));
-        items.add(new Stock("Sun Microsystems", "Sun Microsystems Inc.", "http://www.sun.com", "SUNW", 6.33, -0.01,
-                -0.16));
-        items.add(new Stock("Yahoo", "Yahoo! Inc.", "http://www.yahoo.com", "YHOO", 28.04, -0.17, -0.6));
-
-        Map context = new HashMap<>();
-        context.put("items", items);
-        return context;
-    }
 }
diff --git a/src/test/java/pro/fessional/meepo/bind/dna/DnaBkbTest.java b/src/test/java/pro/fessional/meepo/bind/dna/DnaBkbTest.java
index 22f788e..7c48542 100644
--- a/src/test/java/pro/fessional/meepo/bind/dna/DnaBkbTest.java
+++ b/src/test/java/pro/fessional/meepo/bind/dna/DnaBkbTest.java
@@ -4,8 +4,6 @@
 import pro.fessional.meepo.TraceTest;
 import pro.fessional.meepo.bind.wow.Clop;
 
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
 /**
  * @author trydofor
  * @since 2020-10-22
@@ -14,15 +12,6 @@ public class DnaBkbTest extends TraceTest {
     final DnaBkb d1 = new DnaBkb("// DNA:BKB BlackKingBar", new Clop(0, 14, 1, 1), "BlackKingBar");
     final DnaBkb d2 = new DnaBkb(" // DNA:BKB BlackKingBar", new Clop(1, 15, 1, 1), "BlackKingBar");
 
-    @Test
-    public void testEquals() {
-        assertEquals(d1, d2);
-    }
-
-    @Test
-    public void testHashCode() {
-        assertEquals(d1.hashCode(), d2.hashCode());
-    }
 
     @Test
     public void testToString() {
diff --git a/src/test/java/pro/fessional/meepo/bind/dna/DnaEndTest.java b/src/test/java/pro/fessional/meepo/bind/dna/DnaEndTest.java
index 3033f9d..46fe194 100644
--- a/src/test/java/pro/fessional/meepo/bind/dna/DnaEndTest.java
+++ b/src/test/java/pro/fessional/meepo/bind/dna/DnaEndTest.java
@@ -6,8 +6,6 @@
 
 import java.util.Arrays;
 
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
 /**
  * @author trydofor
  * @since 2020-10-22
@@ -17,16 +15,6 @@ public class DnaEndTest extends TraceTest {
     final DnaEnd d1 = new DnaEnd("// DNA:END BlackKingBar,id", new Clop(0, 17, 1, 1), Arrays.asList("BlackKingBar", "id"));
     final DnaEnd d2 = new DnaEnd(" // DNA:END BlackKingBar,id", new Clop(1, 18, 1, 1), Arrays.asList("id", "BlackKingBar"));
 
-    @Test
-    public void testEquals() {
-        assertEquals(d1, d2);
-    }
-
-    @Test
-    public void testHashCode() {
-        assertEquals(d1.hashCode(), d2.hashCode());
-    }
-
     @Test
     public void testToString() {
         logger.debug("d1={}", d1);
diff --git a/src/test/java/pro/fessional/meepo/bind/dna/DnaRawTest.java b/src/test/java/pro/fessional/meepo/bind/dna/DnaRawTest.java
index 137aba6..9ec28fa 100644
--- a/src/test/java/pro/fessional/meepo/bind/dna/DnaRawTest.java
+++ b/src/test/java/pro/fessional/meepo/bind/dna/DnaRawTest.java
@@ -4,8 +4,6 @@
 import pro.fessional.meepo.TraceTest;
 import pro.fessional.meepo.bind.wow.Clop;
 
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
 /**
  * @author trydofor
  * @since 2020-10-22
@@ -15,15 +13,7 @@ public class DnaRawTest extends TraceTest {
     final DnaRaw d1 = new DnaRaw("// DNA:RAW SUPER(1010100", new Clop(0, 24, 1, 1), 11, 24);
     final DnaRaw d2 = new DnaRaw(" // DNA:RAW SUPER(1010100", new Clop(1, 25, 1, 1), 12, 25);
 
-    @Test
-    public void testEquals() {
-        assertEquals(d1, d2);
-    }
 
-    @Test
-    public void testHashCode() {
-        assertEquals(d1.hashCode(), d2.hashCode());
-    }
 
     @Test
     public void testToString() {
diff --git a/src/test/java/pro/fessional/meepo/bind/dna/DnaSetTest.java b/src/test/java/pro/fessional/meepo/bind/dna/DnaSetTest.java
index 76dfa46..cb6244f 100644
--- a/src/test/java/pro/fessional/meepo/bind/dna/DnaSetTest.java
+++ b/src/test/java/pro/fessional/meepo/bind/dna/DnaSetTest.java
@@ -7,8 +7,6 @@
 
 import java.util.regex.Pattern;
 
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
 /**
  * @author trydofor
  * @since 2020-10-22
@@ -18,16 +16,6 @@ public class DnaSetTest extends TraceTest {
     final DnaSet d1 = new DnaSet("// DNA:SET /false/{{user.male}}/", new Clop(0, 32, 1, 1), Life.nobodyOne(), Pattern.compile("false"), "{{user.male}}");
     final DnaSet d2 = new DnaSet(" // DNA:SET /false/{{user.male}}/", new Clop(1, 33, 1, 1), Life.nobodyOne(), Pattern.compile("false"), "{{user.male}}");
 
-    @Test
-    public void testEquals() {
-        assertEquals(d1, d2);
-    }
-
-    @Test
-    public void testHashCode() {
-        assertEquals(d1.hashCode(), d2.hashCode());
-    }
-
     @Test
     public void testToString() {
         logger.debug("d1={}", d1);
diff --git a/src/test/java/pro/fessional/meepo/bind/rna/RnaPutTest.java b/src/test/java/pro/fessional/meepo/bind/rna/RnaPutTest.java
index 4f091f7..944f2b0 100644
--- a/src/test/java/pro/fessional/meepo/bind/rna/RnaPutTest.java
+++ b/src/test/java/pro/fessional/meepo/bind/rna/RnaPutTest.java
@@ -4,8 +4,6 @@
 import pro.fessional.meepo.TraceTest;
 import pro.fessional.meepo.bind.wow.Clop;
 
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
 /**
  * @author trydofor
  * @since 2020-10-22
@@ -15,16 +13,6 @@ public class RnaPutTest extends TraceTest {
     final RnaPut d1 = new RnaPut("// DNA:PUT os/who/basename $(pwd)/", new Clop(0, 34, 1, 1), "os", "who", "basename $(pwd)", false);
     final RnaPut d2 = new RnaPut(" // DNA:PUT os/who/basename $(pwd)/", new Clop(1, 35, 1, 1), "os", "who", "basename $(pwd)", false);
 
-    @Test
-    public void testEquals() {
-        assertEquals(d1, d2);
-    }
-
-    @Test
-    public void testHashCode() {
-        assertEquals(d1.hashCode(), d2.hashCode());
-    }
-
     @Test
     public void testToString() {
         logger.debug("d1={}", d1);
diff --git a/src/test/java/pro/fessional/meepo/bind/rna/RnaRunTest.java b/src/test/java/pro/fessional/meepo/bind/rna/RnaRunTest.java
index 3ee23c3..aa00d9c 100644
--- a/src/test/java/pro/fessional/meepo/bind/rna/RnaRunTest.java
+++ b/src/test/java/pro/fessional/meepo/bind/rna/RnaRunTest.java
@@ -7,8 +7,6 @@
 
 import java.util.regex.Pattern;
 
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
 /**
  * @author trydofor
  * @since 2020-10-22
@@ -18,16 +16,6 @@ public class RnaRunTest extends TraceTest {
     final RnaRun d1 = new RnaRun("// DNA:RUN os/rand/echo $RANDOM/1-3", new Clop(0, 35, 1, 1), Life.parse("1-3"), "os", Pattern.compile("rand"), "echo $RANDOM", false);
     final RnaRun d2 = new RnaRun(" // DNA:RUN os/rand/echo $RANDOM/1-3", new Clop(1, 36, 1, 1), Life.parse("1-3"), "os", Pattern.compile("rand"), "echo $RANDOM", false);
 
-    @Test
-    public void testEquals() {
-        assertEquals(d1, d2);
-    }
-
-    @Test
-    public void testHashCode() {
-        assertEquals(d1.hashCode(), d2.hashCode());
-    }
-
     @Test
     public void testToString() {
         logger.debug("d1={}", d1);
diff --git a/src/test/java/pro/fessional/meepo/bind/rna/RnaUseTest.java b/src/test/java/pro/fessional/meepo/bind/rna/RnaUseTest.java
index b1fe855..445d3d8 100644
--- a/src/test/java/pro/fessional/meepo/bind/rna/RnaUseTest.java
+++ b/src/test/java/pro/fessional/meepo/bind/rna/RnaUseTest.java
@@ -7,8 +7,6 @@
 
 import java.util.regex.Pattern;
 
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
 /**
  * @author trydofor
  * @since 2020-10-22
@@ -18,16 +16,6 @@ public class RnaUseTest extends TraceTest {
     final RnaUse d1 = new RnaUse("// DNA:USE /meepo/who/1-3", new Clop(0, 25, 1, 1), Life.parse("1-3"), Pattern.compile("meepo"), "who");
     final RnaUse d2 = new RnaUse(" // DNA:USE /meepo/who/1-3", new Clop(1, 26, 1, 1), Life.parse("1-3"), Pattern.compile("meepo"), "who");
 
-    @Test
-    public void testEquals() {
-        assertEquals(d1, d2);
-    }
-
-    @Test
-    public void testHashCode() {
-        assertEquals(d1.hashCode(), d2.hashCode());
-    }
-
     @Test
     public void testToString() {
         logger.debug("d1={}", d1);
diff --git a/src/test/java/pro/fessional/meepo/bind/txt/HiMeepoTest.java b/src/test/java/pro/fessional/meepo/bind/txt/HiMeepoTest.java
index 9b3f924..43aae5a 100644
--- a/src/test/java/pro/fessional/meepo/bind/txt/HiMeepoTest.java
+++ b/src/test/java/pro/fessional/meepo/bind/txt/HiMeepoTest.java
@@ -4,8 +4,6 @@
 import pro.fessional.meepo.TraceTest;
 import pro.fessional.meepo.bind.wow.Clop;
 
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
 /**
  * @author trydofor
  * @since 2020-10-22
@@ -15,16 +13,6 @@ public class HiMeepoTest extends TraceTest {
     final HiMeepo d1 = new HiMeepo("/* H!MEEPO */", new Clop(0, 13, 1, 1), "/*", "*/", true);
     final HiMeepo d2 = new HiMeepo("/* H!MEEPO  */ ", new Clop(0, 14, 1, 1), "/*", "*/", true);
 
-    @Test
-    public void testEquals() {
-        assertEquals(d1, d2);
-    }
-
-    @Test
-    public void testHashCode() {
-        assertEquals(d1.hashCode(), d2.hashCode());
-    }
-
     @Test
     public void testToString() {
         logger.debug("d1={}", d1);
diff --git a/src/test/java/pro/fessional/meepo/bind/txt/TxtSimpleTest.java b/src/test/java/pro/fessional/meepo/bind/txt/TxtSimpleTest.java
index e934800..e1d2dda 100644
--- a/src/test/java/pro/fessional/meepo/bind/txt/TxtSimpleTest.java
+++ b/src/test/java/pro/fessional/meepo/bind/txt/TxtSimpleTest.java
@@ -4,8 +4,6 @@
 import pro.fessional.meepo.TraceTest;
 import pro.fessional.meepo.bind.wow.Clop;
 
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
 /**
  * @author trydofor
  * @since 2020-10-22
@@ -15,16 +13,6 @@ public class TxtSimpleTest extends TraceTest {
     final TxtSimple d1 = new TxtSimple("0123456789", new Clop(1, 10, 1, 1));
     final TxtSimple d2 = new TxtSimple("123456789", new Clop(0, 9, 1, 1));
 
-    @Test
-    public void testEquals() {
-        assertEquals(d1, d2);
-    }
-
-    @Test
-    public void testHashCode() {
-        assertEquals(d1.hashCode(), d2.hashCode());
-    }
-
     @Test
     public void testToString() {
         logger.debug("d1={}", d1);
diff --git a/src/test/java/pro/fessional/meepo/bind/wow/LifeTest.java b/src/test/java/pro/fessional/meepo/bind/wow/LifeTest.java
index d1e0cdb..f2a12e8 100644
--- a/src/test/java/pro/fessional/meepo/bind/wow/LifeTest.java
+++ b/src/test/java/pro/fessional/meepo/bind/wow/LifeTest.java
@@ -86,10 +86,14 @@ public void testAny() {
 
     @Test
     public void testOther() {
-        assertEquals(Life.parse("1"), Life.parse("   1   "));
-        assertEquals(Life.parse("1"), Life.parse("   1,   "));
-        assertEquals(Life.parse("1,3"), Life.parse("   1,3   "));
-        assertEquals(Life.parse("1,3"), Life.parse("   1,3,   "));
-        assertEquals(Life.parse("1-3"), Life.parse("   1-3,   "));
+        equalsLive(Life.parse("1"), Life.parse("   1   "));
+        equalsLive(Life.parse("1"), Life.parse("   1,   "));
+        equalsLive(Life.parse("1,3"), Life.parse("   1,3   "));
+        equalsLive(Life.parse("1,3"), Life.parse("   1,3,   "));
+        equalsLive(Life.parse("1-3"), Life.parse("   1-3,   "));
+    }
+
+    void equalsLive(Life l1, Life l2){
+        assertEquals(l1.toString(), l2.toString());
     }
 }
\ No newline at end of file
diff --git a/src/test/java/pro/fessional/meepo/sack/ParserTest.java b/src/test/java/pro/fessional/meepo/sack/ParserTest.java
index 3475d5f..474d8ba 100644
--- a/src/test/java/pro/fessional/meepo/sack/ParserTest.java
+++ b/src/test/java/pro/fessional/meepo/sack/ParserTest.java
@@ -1,10 +1,12 @@
 package pro.fessional.meepo.sack;
 
+import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 import pro.fessional.meepo.TraceTest;
 import pro.fessional.meepo.bind.Exon;
 import pro.fessional.meepo.bind.dna.DnaEnd;
 import pro.fessional.meepo.bind.dna.DnaSet;
+import pro.fessional.meepo.bind.dna.DnaSon;
 import pro.fessional.meepo.bind.rna.RnaDone;
 import pro.fessional.meepo.bind.rna.RnaEach;
 import pro.fessional.meepo.bind.rna.RnaElse;
@@ -13,6 +15,7 @@
 import pro.fessional.meepo.bind.rna.RnaUse;
 import pro.fessional.meepo.bind.rna.RnaWhen;
 import pro.fessional.meepo.bind.txt.HiMeepo;
+import pro.fessional.meepo.bind.txt.TxtDnaSet;
 import pro.fessional.meepo.bind.txt.TxtSimple;
 import pro.fessional.meepo.bind.wow.Clop;
 import pro.fessional.meepo.bind.wow.Life;
@@ -153,6 +156,7 @@ private void checkDnaSon(HiMeepo meepo, String txt, String build) {
 
         Exon exon = Parser.dealDnaSon(ctx);
         assertNotNull(exon);
+        Assertions.assertTrue(exon.toString().contains(((DnaSon) exon).path));
 
         CharArrayWriter buf = new CharArrayWriter();
         exon.merge(newAcid(ctx), buf);
@@ -274,7 +278,7 @@ private void checkDnaSet(HiMeepo meepo, String txt, Life life, String find, Stri
             DnaSet dna = (DnaSet) exon;
             assertEquals(find, dna.find.pattern());
             assertEquals(repl, dna.repl);
-            assertEquals(life, dna.life);
+            assertEquals(life.toString(), dna.life.toString());
 
             dna.merge(newAcid(ctx), buf);
             assertEquals("", buf.toString());
@@ -312,6 +316,10 @@ public void dealDnaSet() {
 
         checkDnaSet(level5, "@@/* DNA:SET false/{{user.male}}/mail */\n@@", null, null, null, "/* DNA:SET false/{{user.male}}/mail */");
         checkDnaSet(level5, "@@/* DNA:SET /false/mail */\n@@", null, null, null, "/* DNA:SET /false/mail */");
+
+        // nothing to test
+        TxtDnaSet tds = new TxtDnaSet("a", new Clop(0, 1,0,0), "TxtDnaSet-toString-Test");
+        Assertions.assertTrue(tds.toString().contains("TxtDnaSet-toString-Test"));
     }
 
     private void checkRnaRun(HiMeepo meepo, String txt, Life life, String type, String find, String expr, boolean quiet, String build) {
@@ -332,7 +340,7 @@ private void checkRnaRun(HiMeepo meepo, String txt, Life life, String type, Stri
             assertEquals(type, rna.type);
             assertEquals(find, rna.find.pattern());
             assertEquals(expr, rna.expr);
-            assertEquals(life, rna.life);
+            assertEquals(life.toString(), rna.life.toString());
             assertEquals(quiet, rna.mute);
             rna.merge(newAcid(ctx), buf);
             assertEquals("", buf.toString());
@@ -394,7 +402,7 @@ private void checkRnaUse(HiMeepo meepo, String txt, Life life, String find, Stri
             RnaUse rna = (RnaUse) exon;
             assertEquals(find, rna.find.pattern());
             assertEquals(para, rna.para);
-            assertEquals(life, rna.life);
+            assertEquals(life.toString(), rna.life.toString());
 
             Acid ctx1 = newAcid(ctx);
             ctx1.context.put("who", "meepo");
diff --git a/src/test/resources/template/jmh/stocks.meepo-reverse.html b/src/test/resources/template/jmh/stocks.meepo-reverse.html
new file mode 100755
index 0000000..a55d84b
--- /dev/null
+++ b/src/test/resources/template/jmh/stocks.meepo-reverse.html
@@ -0,0 +1,87 @@
+
+
+
+    Stock Prices
+    
+    
+    
+    
+    
+    
+    
+
+
+
+
+
+

Stock Prices

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
#symbolnamepricechangeratio
countitem.symbolitem.nameitem.priceitem.changeitem.ratioitem.changeitem.ratio
+ +