Vortrag-Bad-Honnef/Studienarbeit/content/chapter/02.tex
2024-07-20 01:08:57 +02:00

252 lines
22 KiB
TeX

%!TEX root = ../../main.tex
\chapter{Aufbau der Umgebung}
\section{Voraussetzungen} \label{sec:prequisites}
Der aktuelle \JTXC{} ist in Java implementiert. Da \ac{Java-TX} ein Superset von Java 8\footnote{Es existieren einige Einschränkungen vgl. \cite{plumicke_java-tx_2022}[S.2]} ist, kann der Quellcode im Wesentlichen übernommen werden. Es müssen lediglich die inferierbaren Typinformationen entfernt werden, um die Vorteile von \ac{Java-TX} auch zu nutzen. Da sowohl Java, als auch \ac{Java-TX} Java Bytecode generieren, ist es möglich, \ac{Java-TX} und Java Dateien zu mischen. Dadurch kann der Compiler sukzessive in \ac{Java-TX} übersetzt werden. Der Vorteil daran ist, dass man nach jeder übersetzten Datei einen funktionsfähigen Compiler hat, der zu einem gewissen Grad bereits aus \ac{Java-TX} Dateien besteht. Auf diesem Zwischenstand können dann z.B. auch Tests aufgeführt werden, um die Funktionalität zu prüfen.
Ein Problem besteht darin, dass der \JTXC{} \texttt{.jav}\footnote{\ac{Java-TX} Quelldateien haben die Dateiendung \texttt{.jav}}-Dateien und \texttt{.class}-Dateien lesen kann, während der \gls{javac} Compiler \texttt{.java}-Dateien und \texttt{.class}-Dateien einlesen kann. Jedoch kann der \JTXC{} keine \texttt{.java}-Dateien lesen und der \gls{javac} Compiler natürlich keine \texttt{.jav}-Dateien. Das bedeutet, dass zirkuläre Abhängigkeiten zwischen Java und \ac{Java-TX} Dateien nicht ohne Weiteres möglich sind. Dieses Problem ist in \autoref{fig:circular_dependency} visualisiert. Ein Pfeil symolisieren dabei Abhängigkeiten zu einer anderen Datei. Wenn der \JTXC{} zuerst aufgerufen wird, kann er die Java Datei nicht lesen und umgekehrt. Selbst wenn die Abhängigkeiten nicht zirkulär sind, muss die genaue Reihenfolge der Kompilierung definiert sein. Dies bedeutet das der Build-Prozess für den \JTXCinJTX{} sehr komplex werden würde.
\begin{figure}[H]
\centering
\begin{tikzpicture}[node distance=2cm,thick]
% Nodes
\node[draw, circle] (file1) {file1.java};
\node[draw, circle, right=of file1] (file2) {file2.jav};
% Arrows
\draw[->] (file1) to[bend left=45] (file2);
\draw[->] (file2) to[bend left=45] (file1);
\end{tikzpicture}
\caption{Zirkuläre Abhängigkeiten zwischen Java und \ac{Java-TX} Dateien}
\label{fig:circular_dependency}
\end{figure}
Aktuell wird dieses Problem gelöst indem der gesamten Compiler zu Beginn einmal in seiner Ursprungsform (also nur \texttt{.java} Dateien) mit \gls{javac} kompiliert wird. Dann liegt der komplette \JTXC{} in Form von \texttt{.class} Dateien vor. Anschließend muss diese Hierarchie nur noch als Classpath des \JTXC{} angegeben werden. Der \JTXC{} kann nun die \texttt{.class} Dateien lesen, die zur jeweiligen \texttt{.java} Datei korrespondieren. Wichtig ist an dieser Stelle natürlich, dass die \texttt{.class}-Dateien den gleichen Stand, wie die Quelldateien haben. Dieses Verhalten ist in \autoref{fig:circular_dependency_resolved} gezeigt. Die Grafik zeigt, dass der \JTXC{} zur Kompilierung der \texttt{.jav} Datei die zuvor generierte \texttt{.class} Datei von \texttt{file1} verwendet. Wenn später dann \texttt{file1} von \gls{javac} kompiliert wird, wurde \texttt{file2.class} bereits vom \JTXC{} erstellt. Der \gls{javac} Compiler benötigt die vorkompilierten Klassen also nicht mehr im Classpath und kann die vom \JTXC{} generierten .class Dateien verwenden. Natürlich ist es für diese Lösung notwendig, dass der \JTXC{} zuerst alle \lstinline|.jav| Dateien kompiliert, bevor der \gls{javac} Compiler auf Basis der generierten \texttt{.class} Dateien, alle \texttt{.java} kompiliert.
Diese Reihenfolge ist aber ohnehin notwendig, damit \gls{javac} seinen Bytecode mit den korrekten Typen generieren kann, die der \JTXC{} inferiert hat.
Diese Lösung ist zwar nicht ideal, da man eine Abhängigkeit zum gesamten vorkompilierten Projekt benötigt. Da der \JTXC{} aber keine \texttt{.java} Dateien lesen kann, ist es vermutlich die einzige Möglichkeit, zirkuläre Abhängigkeiten zwischen \texttt{.java} und \texttt{.jav} Dateien zu lösen.
\begin{figure}[h]
\centering
\begin{tikzpicture}[node distance=2cm, thick]
% Nodes
\node[draw, circle] (file1) {file1.java};
\node[draw, circle, right=of file1] (file2) {file2.jav};
\node[draw, circle, below=0.7cm of file1] (file1_class) {file1.class};
% Arrows
\draw[->] (file1) to[bend left=45] (file2);
\draw[->, dashed] (file2) to[bend left=45] (file1);
\draw[->] (file2) -- (file1_class);
\begin{scope}[on background layer]
\node[fit=(file1_class), draw, fill=gray!20, inner sep=0.5cm] {};
\end{scope}
\end{tikzpicture}
\caption{Zirkuläre Abhängigkeiten zwischen Java und \ac{Java-TX} Dateien behoben}
\label{fig:circular_dependency_resolved}
\end{figure}
Die aktuelle \JTXC{} Implementierung automatisiert den Build-Prozess mit Maven. Java ist für die Verwendung von Java basierten Sprachen entwickelt worden \cite{noauthor_maven_nodate}. Da Maven keine \ac{Java-TX} Unterstützung bietet, muss der Build-Prozess für den \JTXCinJTX{} manuell angepasst werden. Dazu wurden zwei Lösungsansätze erarbeitet, die in den folgenden Abschnitten beschrieben werden.
\section{Kompilierung mit Make}
Der erste Ansatz der Verfolgt wurde, war die Kompilierung mittels \ac{GNU} make (vgl. \autoref{sec:make}) umzusetzen. Make ist sehr flexibel und ist vielseitig einsetzbar. Um nicht alle Quelldateien einzeln angeben zu müssen, werden Wildcards verwendet. Außerdem bietet make den Vorteil, das nur geänderte Dateien neu kompiliert werden. Dies ist besonders bei großen Projekten von Vorteil, da nicht alle Dateien neu kompiliert werden müssen. Das verwendete Makefile ist in \autoref{lst:makefile} zu sehen.
\begin{lstlisting}[caption={Makefile für die Kompilierung des \JTXCinJTX{}}, label={lst:makefile}, language=make]
JFLAGS = -g:none -implicit:none -nowarn
JC = javac
JTX = JavaTXcompiler-1.0-jar-with-dependencies.jar
SRCDIR = javatx-src/main/java
DESTDIR = out
# Use find to locate all .java and .jav files recursively
JAVASOURCES := $(shell find $(SRCDIR) -name '*.java')
JAVSOURCES := $(shell find $(SRCDIR) -name '*.jav')
# Convert .java files to .class files with the same directory structure
JAVACLASSES := $(patsubst $(SRCDIR)/%.java,$(DESTDIR)/%.class,$(JAVASOURCES))
JAVCLASSES := $(patsubst $(SRCDIR)/%.jav,$(DESTDIR)/%.class,$(JAVSOURCES))
default: $(JAVCLASSES) $(JAVACLASSES)
# Rule for compiling .jav files
$(DESTDIR)/%.class: $(SRCDIR)/%.jav
java -jar $(JTX) -d "$(DESTDIR)" -cp "$(SRCDIR):$(DESTDIR):target/dependencies/" $<
# Rule for compiling .java files
$(DESTDIR)/%.class: $(SRCDIR)/%.java
$(JC) -d $(DESTDIR) -cp "$(SRCDIR):$(DESTDIR):target/dependencies/*" $(JFLAGS) $<
.PHONY: clean
clean:
$(RM) -r $(DESTDIR)/*
\end{lstlisting}
Im ersten Abschnitt werden einige globale Variablen, wie den Ort des \gls{javac} und \JTXC{} und der Quell- und Zielordner definiert. Dann werden mit dem shell Befehl \texttt{find} alle Dateien die auf .java und .jav enden in den Variablen \texttt{JAVASOURCES} und \texttt{JAVSOURCES} gespeichert. Der \texttt{find} Befehl geht dabei rekursiv vor und beachtet auch alle Unterverzeichnisse des Quellverzeichnisse. In diesem Fall findet der Befehl also alle Dateien in der Packethierarchie. Danach wird der \texttt{patsubst} Befehl von make verwendet, um den Pfand, an dem die korrespondierende \texttt{.class} Datei liegen wird, zu substituieren. So wird z.B. die Liste der Quelldateien
\begin{verbatim}
javatx-src/main/java/de/dhbwstuttgart/typeinference/assumptions/Assumption.java
javatx-src/main/java/de/dhbwstuttgart/typeinference/assumptions/FieldAssumption.java
\end{verbatim}
zu einer Liste mit folgenden korrespondierenden Zieldateien
\begin{verbatim}
out/de/dhbwstuttgart/typeinference/assumptions/Assumption.class
out/de/dhbwstuttgart/typeinference/assumptions/FieldAssumption.class
\end{verbatim}
Die genaue Syntax des \lstinline|patsubst| Befehls kann in \cite{stallman_gnu_2004}[S.92] nachgelesen werden.
In der nächsten Zeile wird die Standardregel für das Makefile definiert. In \ac{GNU} make ist die erste Regel diejenige, die aufgerufen wird, wenn der make Befehl ohne Parameter aufgerufen wird \cite{stallman_gnu_2004}[S.109]. Diese Regel versucht alle Dateien in \lstinline|$(JAVCLASSES)| und \lstinline|$(JAVSOURCES)| zu kompilieren. Dazu werden die nächste beiden Regeln verwendet.
Die erste Regel kompiliert \ac{Java-TX} Dateien. Dazu liegt der Compiler als JAR-Archiv vor. Der Compiler wird mit dem Befehl \lstinline|java -jar| aufgerufen, welcher die Ausführung von JAR-Archiven ermöglicht. Der \texttt{-d} Parameter gibt den Zielordner an, in dem die \texttt{.class} Datei gespeichert wird. Der \texttt{-cp} Parameter gibt die Klassenpfade an, die der Compiler benötigt. In diesem Fall sind das der Quellordner, der Zielordner und die Abhängigkeiten des Projekts. Der \lstinline|$<| Operator gibt den Pfad der ersten Abhängigkeit an \cite{stallman_gnu_2004}[S.131]. In diesem Fall ist das die \texttt{.jav} Quelldatei. Das \texttt{\%} Symbol dient als Wildcard und steht für eine beliebige Zeichenkette. Eine solche Regeln nennt man auch Musterregel (engl. Pattern Rule) \cite{stallman_gnu_2004}[S.129].
Die zweite Regel kompiliert \lstinline|.java| Quelldateien. Sie unterscheidet sich nur marginal von der ersten Regel. Lediglich der Aufruf des Compilers und die Dateiendung der Regel wurden angepasst, da hierzu der \gls{javac} Compiler verwendet wird. Die Parameter \lstinline|-d| und \lstinline|-cp| sind identisch. Lediglich einige weitere Parameter, die Anfags in die Variable \lstinline|JFLAGS| gespeichert wurden, werden dem Compiler übergeben. Der Parameter |g:none| bedeutet, dass keine Debuginformationen generiert werden sollen. Das macht den generierten Bytecode in solch einem Entwicklungsumfeld leichter lesbar. Der Parameter |implicit:none| bedeutet, dass Abhängigkeit nicht impliziert kompiliert werden sollen. Weiteres dazu in \autoref{sec:performance}. Der Parameter \lstinline|nowarn| bedeutet schließlich, dass keine Warnungen ausgegeben werden sollen.
Die letzte Regel des Makefiles ist eine Phony Regel (vgl. \autoref{sec:make}). Sie löscht den Inhalt des Zielordners. Dazu wird der \lstinline|rm| Befehl mit dem \lstinline|-r| Parameter verwendet, um rekursiv alle Dateien und Ordner zu löschen. Der \lstinline|*| Operator steht für alle Dateien und Ordner im Zielordner. Die Variable RM ist eine vordefinierte Variable in make, die den Befehl zum Löschen von Dateien definiert \cite{stallman_gnu_2004}[S. 125 ff.].
Die Regel wird ausgeführt, wenn der make Befehl mit dem Parameter \lstinline|clean| aufgerufen wird.
\subsection{Performanceprobleme} \label{sec:performance}
Das Makefile ist funktionstüchtig und kompiliert das Projekt korrekt, allerdings geht eine komplette Kompilierung des Projekts mehrere Minuten, während die originale Java Version des Compilers nur wenige Sekunden benötigt. Dabei ist zu erwähnen, dass ein Großteil der Performanceeinbußen nicht durch die Typinferenz des \JTXC{} entsteht, sondern schon der \gls{javac} Compiler deutlich langsamer als beim ursprünglichen Compiler ist. Dies hängt damit zusammen, dass der \gls{javac} Compiler für jede Datei einzeln aufgerufen wird. Dies resultierte vor allem auf Grund folgender zwei Gründe in einer schlechteren Performance:
\begin{itemize}
\item Der \gls{javac} Compiler kompiliert standardmäßig alle Abhängigkeiten implizit mit. Da der Compiler aber sowieso für jede Datei aufgerufen wird, bedeutet das, dass viele Dateien mehrfach kompiliert werden.
\item Der Overhead den Compiler zu starten ist relativ hoch. Das liegt zu großen Teilen wahrscheinlich daran, dass der \gls{javac} Compiler in Java implementiert ist und somit die \ac{JVM} gestartet werden muss. Dadurch ist es sehr ineffizient den Compiler für jede Datei neu zu starten.
\end{itemize}
Die implizite Kompilierung kann beim Aufruf von \gls{javac} durch den Parameter \lstinline|-implicit:none| deaktiviert werden. Dieser sorgt dafür, dass Abhängigkeiten nicht implizit kompiliert werden. In \autoref{tab:compiletimes_javac} sind die Kompilierzeiten des \JTXC{} mit \gls{javac} aufgeführt.
\begin{table}[h]
\centering
\begin{tabular}{|l|l|l|l|}
\hline
& Einzeln & Einzeln (Nicht Implizit) & Gemeinsam \\ \hline
Min & 04:52,44 & 02:23,28 & 00:02,50 \\ \hline
Max & 05:10,69 & 02:25,78 & 00:02,73 \\ \hline
Avg & 04:58,83 & 02:24,15 & 00:02,57 \\ \hline
\end{tabular}
\caption{Kompilierzeiten des \JTXC{} mit \gls{javac}\footnote{Diese Zeiten wurden mit einem Intel i5-12400F Prozessor und 16GB RAM auf einem Linux Rechner mit dem \ac{GNU} Time Befehl gemessen. Jeder Test wurde 5x durchgeführt. Der Durchschnitt wurde aus allen Durchläufen berechnet.}}
\label{tab:compiletimes_javac}
\end{table}
Die erste Spalte beschreibt die Kompilierzeiten, wenn der Compiler für jede Datei einzeln aufgerufen wird und die implizite Kompilierung von Abhängigkeiten aktiviert ist. Die zweite Spalte beschreibt die Kompilierzeiten, wenn der Compiler für jede Datei einzeln aufgerufen wird, aber die Abhängigkeiten nicht implizit kompiliert werden. Es werden also mehrfache Kompilierungen vermieden. Die dritte Spalte beschreibt die Kompilierzeiten, wenn der Compiler für alle Dateien auf einmal aufgerufen wird. Zu sehen ist, dass sich die Kompilierzeit in etwa halbiert, wenn die Abhängigkeiten nicht implizit kompiliert werden. Allerdings ist die letzte Variante, die alle Dateien auf einmal an den Compiler übergibt, immer noch etwa um den Faktor 56 schneller.
Aus diesem Grund wurde entschieden, dass die Kompilierung des \JTXC{} mit make nicht praktikabel ist.
Ein alternativer Ansatz, der dieses Problem umgeht, wird in \autoref{sec:script} vorgestellt.
\section{Kompilierung mit Bash} \label{sec:script}
Aufgrund der Laufzeiteinbusen, die beim Verwenden des Makefiles zustande kommen, musste eine alternative Lösung gefunden werden. Die Idee ist zuerst alle Dateien, die kompiliert werden müssen zu sammeln und dann den Compiler nur einmal aufzurufen. Da sich die Komplexität für solch ein Skript in Grenzen hält und die größtmögliche Flexibilität bietet, wurde sich für ein eigenes Skript entschieden. Als Skriptsprache wurde \ac{Bash} ausgewählt. Diese ist sowohl unter Linux, als auch unter MacOS nativ ohne weitere Abhängigkeiten lauffähig. Zur Ausführung auf Windows müsste man auf das \ac{WSL} zurückgreifen.
Das Skript hat das gleiche Verhalten wie das Makefile. Der einzige Unterschied besteht darin, dass alle Dateien die kompiliert werden müssen in eine Liste gespeichert werden und der Compiler am Ende nur einmal aufgerufen wird.
Dadurch wird der Overhead des Compilers minimiert und die Kompilierzeit deutlich reduziert, sodass man ohne \ac{Java-TX} Dateien mit dem Skript eine Zeit von etwa 2 Sekunden erreicht, was in etwa der Zeit in \autoref{tab:compiletimes_javac} entspricht. Wenn das Skript mit dem Argument \lstinline|clean| aufgerufen wird, wird der Inhalt des Zielordners gelöscht.
\begin{figure}[h!]
\begin{forest}
for tree={
font=\ttfamily,
grow'=0,
child anchor=west,
parent anchor=south,
anchor=west,
calign=first,
edge path={
\noexpand\path [draw, \forestoption{edge}]
(!u.south west) ++(1pt,0) |- (.child anchor)\forestoption{edge label};
},
before typesetting nodes={
if n=1
{insert before={[,phantom]}}
{}
},
fit=band,
before computing xy={l=15pt},
inner sep=2pt,
l=15pt,
},
folder/.style={font=\faFolder\space, edge+={color=blue!50}},
folder-open/.style={font=\faFolderOpen\space, edge+={color=blue!50}},
file/.style={font=\faFile\space, edge+={color=red!50}},
jar/.style={font=\faFileArchive\space, edge+={color=green!50}},
markdown/.style={font=\faMarkdown\space, edge+={color=purple!50}},
script/.style={font=\faFileCode\space, edge+={color=orange!50}},
[JavaTXCompilerInJavaTXNoMaven, folder-open
[JavaTXcompiler.jar, jar]
[javatx-src, folder]
[tests, folder]
[out, folder-open
[src, folder]
[tests, folder]
]
[lib, folder-open
[dependencies, folder]
[resources, folder]
[classes, folder]
]
[test.sh, script]
[compile.sh, script]
[README.md, markdown]
]
\end{forest}
\caption{Dateistruktur des Projekts}
\label{fig:folder_structure}
\end{figure}
In \autoref{fig:folder_structure} ist die Ordnerstruktur mit allen notwendigen Ressourcen des Projekts dargestellt. Das Projekt besteht aus folgenden Ordnern und Dateien:
\begin{itemize}
\item \textbf{JavaTXCompiler.jar}: Die aktuelle Version des \JTXC{} in Form eines JAR-Archivs. Es wird verwendet, um die \ac{Java-TX} Dateien des \JTXCinJTX{} zu kompilieren.
\item \textbf{javatx-src}: Enthält den Code des \JTXCinJTX. Besteht zum Großteil aus den Java Quelldateien des \JTXC{} und einigen \ac{Java-TX} Dateien, die bereits migriert werden konnten.
\item \textbf{tests}: Enthält die Testsuite des \JTXC{} in Form von \gls{JUnit} Tests (vgl. \autoref{sec:tests}).
\item \textbf{out}: Enthält das kompilierte Projekt, sowie die kompilierten Tests.
\begin{itemize}
\item \textbf{src}: Enthält das kompilierte Projekt in Form von \texttt{.class} Dateien.
\item \textbf{test}: Enthält die kompilierten Tests in Form von \texttt{.class} Dateien.
\end{itemize}
\item \textbf{lib}: Enthält zusätzliche statische Dateien, die für das Projekt benötigt werden.
\begin{itemize}
\item \textbf{dependencies}: Enthält externe Bibliotheken, die für das Projekt benötigt werden in Form von JAR-Archiven. Werden im \JTXC{} durch \gls{Maven} verwaltet.
\item \textbf{resources}: Enthält zusätzliche Ressourcen, die für die Tests benötigt werden. Im Wesentlichen sind dies die Beispielprogramme, die getestet werden.
\item \textbf{classes}: Enthält den gesamten \JTXC{} in kompilierter Form um Abhängigkeiten zwischen \texttt{jav} und \texttt{java} Dateien zu ermöglichen (vgl. \autoref{sec:prequisites})
\end{itemize}
\item \textbf{test.sh}: Dieses Bash Skript ist für die Kompilierung und Ausführung der Tests zuständig.
\item \textbf{compile.sh}: Dieses Bash Skript ist für die Kompilierung des Projekts zuständig ist.
\item \textbf{README.md}: Eine Markdown Datei, die Informationen zur Verwendung des Projekts enthält.
\end{itemize}
Da das gesamte Skript weder besonders spannend, noch komplex und zusätzlich relativ lang ist, wird es hier nicht im Detail beschrieben. Es ist jedoch im Anhang unter \autoref{app:bash} zu finden.
\section{Tests} \label{sec:tests}
Da es bei der Entwicklung eines eigenen Compilers schnell zu fehlerhaftem Bytecode kommen kann, welcher ggf. erst zur Laufzeit auffällt, ist es sinnvoll den generierten Bytecode zu testen.
Der große Vorteil ist an dieser Stelle, dass es bereits eine Testsuite mit über 200 Unittests für den \JTXC{} gibt. Diese Tests müssen lediglich auf den generierten Bytecode des \JTXCinJTX{} angewandt werden, um fehlerhafter Bytecode oft frühzeitig zu erkennen.
Die Tests des \JTXC{} werden über \gls{Maven} verwaltet und ausgeführt. Außerdem bieten die meisten \ac{IDE}s eine direkte Integration für \gls{JUnit} Tests. Diese automatischen Verfahren sind für reine Java Projekte geeignet, da sie die Testsuite automatisch ausführen und die Ergebnisse anzeigen. Da der \JTXCinJTX{} aus einer gemischten Codebasis mit Java und \ac{Java-TX} Quelldateien besteht, die mit dem eigenen Skript kompiliert wird, ist es nicht möglich diese Tools zu verwenden.
Daher muss die Testsuite manuell auf die kompilierten Dateien angewandt werden. Um diesen Prozess zu automatisieren wurde ein weiteres Skript geschrieben. Dieses Skript kompiliert und kopiert die notwendigen Dateien zur Ausführung der Tests in den korrekten Ordner. Danach können die Tests mit einem Aufruf des \gls{JUnit} 4 JAR-Archivs ausgeführt werden.
Das gesamte Skript ist in \autoref{lst:testscript} zu sehen.
\begin{lstlisting}[caption={Skript zum Kompilieren und Ausführen der Tests}, label={lst:testscript}, language=bash]
#!/bin/bash
##TEST ENVIRONMENT##
DESTDIR="out/src"
TESTDESTDIR="out/tests"
DEPENDENCIES="dependencies/*"
TESTFILES="TestComplete TestPackages GenericParserTest TestTypeDeployment finiteClosure.SuperInterfacesTest astfactory.ASTFactoryTest targetast.ASTToTypedTargetAST targetast.GreaterEqualTest targetast.GreaterThanTest targetast.InheritTest2 targetast.InheritTest targetast.LessEqualTest targetast.LessThanTest targetast.OLTest targetast.PostIncTest targetast.PreIncTest targetast.PutTest targetast.TestCodegen targetast.TestGenerics targetast.TphTest targetast.WhileTest"
RESOURCES="lib/resources"
#recompile all necessary test files
javac -cp "$TESTDESTDIR:$DESTDIR:$DEPENDENCIES" -d $TESTDESTDIR tests/**/*.java
javac -cp "$TESTDESTDIR:$DESTDIR:$DEPENDENCIES" -d $TESTDESTDIR tests/*.java
cp -r $RESOURCES $TESTDESTDIR/resources/
cd "$TESTDESTDIR"
#run tests with junit
java -cp "../src:.:../../dependencies/*" org.junit.runner.JUnitCore $TESTFILES
\end{lstlisting}
Zu Beginn werden einige Variablen initialisiert. \texttt{DESTDIR} gibt den Ordner an, an dem die bereits kompilierten Dateien des \JTXCinJTX{} liegen. Auf diesen Dateien werden die Tests ausgeführt. \texttt{TESTDESTDIR} gibt den Ordner an, in dem die kompilierten \gls{JUnit} Testdateien abgelegt werden sollen. \texttt{DEPENDENCIES} gibt den Ordner mit den externen Abhängigkeiten des Projekts an. Diese sind identisch zu den Abhängigkeiten des \texttt{compile.sh} Skripts und damit die Dateien, die im ursprünglichen Compiler über \gls{Maven} verwaltet werden. \texttt{TESTFILES} gibt die Testklassen an, die ausgeführt werden sollen. Zuletzt verweist \texttt{RESOURCES} auf den Ordner, in dem die Testdateien, d.h. \texttt{.jav} Quelldateien, die von den Tests kompiliert werden, liegen.
Dann werden alle \gls{JUnit} Test Dateien kompiliert, wenn sie nicht bereits vorhanden sind. Im Anschluss werden sämtliche Ressourcen, die von den Tests benötigt werden, z.B. die Testdateien, deren Code kompiliert werden soll, an die korrekte Position kopiert, sodass die Tests diese zur Laufzeit finden können. In den nächsten Zeilen wird in den Testordner gewechselt und die Tests mit der Klasse \texttt{JUnitCore} ausgeführt. Die Klasse \texttt{org.juni.runner.JUnitCore} ist die Hauptklasse von \gls{JUnit} 4, die die Tests ausführt. Sie ist im \gls{JUnit}4 JAR-Archiv enthalten, welches bereits bei den Abhängigkeiten in \texttt{DEPENDENCIES} vorhanden ist.