Permalink
Browse files

initial import

  • Loading branch information...
1 parent e920d0a commit be039cdfd5b85c28ab76aee2079237c0968da798 @gojko gojko committed Jan 15, 2009
View
10 .classpath
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" output="target/classes" path="src/main/java"/>
+ <classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources"/>
+ <classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
+ <classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+ <classpathentry kind="con" path="org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER"/>
+ <classpathentry kind="output" path="target/classes"/>
+</classpath>
View
23 .project
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>transactionalrunner</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.maven.ide.eclipse.maven2Builder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ <nature>org.maven.ide.eclipse.maven2Nature</nature>
+ </natures>
+</projectDescription>
View
8 .settings/org.maven.ide.eclipse.prefs
@@ -0,0 +1,8 @@
+#Tue Jan 06 08:18:37 GMT 2009
+activeProfiles=
+eclipse.preferences.version=1
+fullBuildGoals=process-test-resources
+includeModules=false
+resolveWorkspaceProjects=true
+resourceFilterGoals=process-resources resources\:testResources
+version=1
View
5 README
@@ -0,0 +1,5 @@
+Transactional SLIM/FIT runner for FitNesse/Java
+
+This is an implementation of the Slim test runner for Fitnesse that wraps all tests into spring transactions and rolls back on the end of each test, to make data-driven tests instantly repeatable with minimal code and no configuration changes in the fixtures or the Spring context.
+
+for usage instructions including configuration see http://gojko.net/2009/01/14/transactional-springslim-test-runner/
View
71 pom.xml
@@ -0,0 +1,71 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>info.fitnesse</groupId>
+ <artifactId>transactionalrunner</artifactId>
+ <version>1.0.2</version>
+ <dependencies>
+ <dependency>
+ <groupId>org.fitnesse</groupId>
+ <artifactId>fitnesse</artifactId>
+ <version>20090112</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring</artifactId>
+ <version>2.5.5</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>4.4</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>mysql</groupId>
+ <artifactId>mysql-connector-java</artifactId>
+ <version>5.1.6</version>
+ <scope>test</scope>
+ </dependency>
+
+ </dependencies>
+ <repositories>
+ <repository>
+ <id>maven-central</id>
+ <url>http://repo1.maven.org/maven2/</url>
+ <snapshots>
+ <enabled>true</enabled>
+ </snapshots>
+ <releases>
+ <enabled>true</enabled>
+ </releases>
+ </repository>
+ </repositories>
+ <pluginRepositories>
+ <pluginRepository>
+ <id>maven-central</id>
+ <url>http://repo1.maven.org/maven2/</url>
+ <snapshots>
+ <enabled>true</enabled>
+ </snapshots>
+ <releases>
+ <enabled>true</enabled>
+ </releases>
+ </pluginRepository>
+ </pluginRepositories>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>2.0.2</version>
+ <configuration>
+ <source>1.5</source>
+ <target>1.5</target>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
View
27 src/main/java/info/fitnesse/FitnesseSpringContext.java
@@ -0,0 +1,27 @@
+package info.fitnesse;
+
+import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.support.GenericApplicationContext;
+import org.springframework.core.io.ClassPathResource;
+
+public class FitnesseSpringContext {
+ private static ApplicationContext instance;
+ public static ApplicationContext getInstance(){
+ if (instance!=null) return instance;
+ if (System.getProperty("spring.context")==null){
+ throw new Error("spring.context environment variable is not defined. please set it to your spring context path");
+ }
+ GenericApplicationContext ctx = new GenericApplicationContext();
+ XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(ctx);
+ xmlReader.loadBeanDefinitions(System.getProperty("spring.context"));
+ xmlReader = new XmlBeanDefinitionReader(ctx);
+ xmlReader.loadBeanDefinitions(new ClassPathResource("embedded-rollback.xml"));
+ ctx.refresh();
+ instance=ctx;
+ return instance;
+ }
+ public static RollbackIntf getRollbackBean() {
+ return (RollbackIntf) getInstance().getBean("rollbackBean");
+ }
+}
View
11 src/main/java/info/fitnesse/RollbackBean.java
@@ -0,0 +1,11 @@
+package info.fitnesse;
+import org.springframework.transaction.annotation.Transactional;
+
+
+public class RollbackBean implements RollbackIntf {
+ @Transactional(rollbackFor=RollbackNow.class)
+ public void process(Runnable r) {
+ r.run();
+ throw new RollbackNow();
+ }
+}
View
5 src/main/java/info/fitnesse/RollbackIntf.java
@@ -0,0 +1,5 @@
+package info.fitnesse;
+
+public interface RollbackIntf {
+ public void process(Runnable r);
+}
View
5 src/main/java/info/fitnesse/RollbackNow.java
@@ -0,0 +1,5 @@
+package info.fitnesse;
+
+public class RollbackNow extends RuntimeException {
+
+}
View
233 src/main/java/info/fitnesse/TransactionalFitServer.java
@@ -0,0 +1,233 @@
+package info.fitnesse;
+import java.io.*;
+import java.net.Socket;
+
+import fit.*;
+import fitnesse.components.CommandLine;
+import fitnesse.components.FitProtocol;
+import fitnesse.util.StreamReader;
+
+public class TransactionalFitServer {
+ public class DocumentRunner implements Runnable {
+ private int size;
+
+ public DocumentRunner(int size) {
+ this.size = size;
+ }
+
+ public void run() {
+ try {
+ print("processing document of size: " + size + "\n");
+ String document = FitProtocol.readDocument(socketReader, size);
+ Parse tables = new Parse(document);
+ newFixture().doTables(tables);
+ print("\tresults: " + fixture.counts() + "\n");
+ counts.tally(fixture.counts);
+ } catch (Exception e) {
+ exception(e);
+ }
+ }
+ }
+
+ public String input;
+
+ public Fixture fixture = new Fixture();
+
+ public FixtureListener fixtureListener = new TablePrintingFixtureListener();
+
+ private Counts counts = new Counts();
+
+ private OutputStream socketOutput;
+
+ private StreamReader socketReader;
+
+ private boolean verbose = false;
+
+ private String host;
+
+ private int port;
+
+ private int socketToken;
+
+ private Socket socket;
+
+ public TransactionalFitServer(String host, int port, boolean verbose) {
+ this.host = host;
+ this.port = port;
+ this.verbose = verbose;
+ }
+
+ public TransactionalFitServer() {
+ }
+
+ public static void main(String argv[]) throws Exception {
+ TransactionalFitServer fitServer = new TransactionalFitServer();
+ fitServer.run(argv);
+ System.exit(fitServer.exitCode());
+ }
+
+ public void run(String argv[]) throws Exception {
+ args(argv);
+ establishConnection();
+ validateConnection();
+ process();
+ closeConnection();
+ exit();
+ }
+
+ public void closeConnection() throws IOException {
+ socket.close();
+ }
+
+ public void process() {
+
+ RollbackIntf rollbackProcessingBean = FitnesseSpringContext.getRollbackBean();//
+ fixture.listener = fixtureListener;
+ try {
+ int size = 1;
+ while ((size = FitProtocol.readSize(socketReader)) != 0) {
+ try {
+ rollbackProcessingBean.process(new DocumentRunner(size));
+ } catch (RollbackNow rn) {
+ print("rolling back now" + "\n");
+ }
+ }
+ print("completion signal recieved" + "\n");
+ } catch (Exception e) {
+ exception(e);
+ }
+ }
+
+ public String readDocument() throws Exception {
+ int size = FitProtocol.readSize(socketReader);
+ return FitProtocol.readDocument(socketReader, size);
+ }
+
+ protected Fixture newFixture() {
+ fixture = new Fixture();
+ fixture.listener = fixtureListener;
+ return fixture;
+ }
+
+ public void args(String[] argv) {
+ CommandLine commandLine = new CommandLine("[-v] host port socketToken");
+ if (commandLine.parse(argv)) {
+ host = commandLine.getArgument("host");
+ port = Integer.parseInt(commandLine.getArgument("port"));
+ socketToken = Integer.parseInt(commandLine
+ .getArgument("socketToken"));
+ verbose = commandLine.hasOption("v");
+ } else
+ usage();
+ }
+
+ private void usage() {
+ System.out
+ .println("usage: java test.RollbackServer [-v] host port socketTicket");
+ System.out.println("\t-v\tverbose");
+ System.exit(-1);
+ }
+
+ protected void exception(Exception e) {
+ print("Exception occurred!" + "\n");
+ print("\t" + e.getMessage() + "\n");
+ Parse tables = new Parse("span", "Exception occurred: ", null, null);
+ fixture.exception(tables, e);
+ counts.exceptions += 1;
+ fixture.listener.tableFinished(tables);
+ fixture.listener.tablesFinished(counts); // TODO shouldn't this be
+ // fixture.counts
+ }
+
+ public void exit() throws Exception {
+ print("exiting" + "\n");
+ print("\tend results: " + counts.toString() + "\n");
+ }
+
+ public int exitCode() {
+ return counts.wrong + counts.exceptions;
+ }
+
+ public void establishConnection() throws Exception {
+ establishConnection(makeHttpRequest());
+ }
+
+ public void establishConnection(String httpRequest) throws Exception {
+ socket = new Socket(host, port);
+ socketOutput = socket.getOutputStream();
+ socketReader = new StreamReader(socket.getInputStream());
+ byte[] bytes = httpRequest.getBytes("UTF-8");
+ socketOutput.write(bytes);
+ socketOutput.flush();
+ print("http request sent" + "\n");
+ }
+
+ private String makeHttpRequest() {
+ return "GET /?responder=socketCatcher&ticket=" + socketToken
+ + " HTTP/1.1\r\n\r\n";
+ }
+
+ public void validateConnection() throws Exception {
+ print("validating connection...");
+ int statusSize = FitProtocol.readSize(socketReader);
+ if (statusSize == 0)
+ print("...ok" + "\n");
+ else {
+ String errorMessage = FitProtocol.readDocument(socketReader,
+ statusSize);
+ print("...failed because: " + errorMessage + "\n");
+ System.out.println("An error occurred while connecting to client.");
+ System.out.println(errorMessage);
+ System.exit(-1);
+ }
+ }
+
+ public Counts getCounts() {
+ return counts;
+ }
+
+ private void print(String message) {
+ if (verbose)
+ System.out.print(message);
+ }
+
+ public static byte[] readTable(Parse table) throws Exception {
+ ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();
+ OutputStreamWriter streamWriter = new OutputStreamWriter(byteBuffer,
+ "UTF-8");
+ PrintWriter writer = new PrintWriter(streamWriter);
+ Parse more = table.more;
+ table.more = null;
+ if (table.trailer == null)
+ table.trailer = "";
+ table.print(writer);
+ table.more = more;
+ writer.close();
+ return byteBuffer.toByteArray();
+ }
+
+ public void writeCounts(Counts count) throws IOException {
+ // TODO This can't be right.... which counts should be used?
+ FitProtocol.writeCounts(counts, socketOutput);
+ }
+
+ class TablePrintingFixtureListener implements FixtureListener {
+ public void tableFinished(Parse table) {
+ try {
+ byte[] bytes = readTable(table);
+ if (bytes.length > 0)
+ FitProtocol.writeData(bytes, socketOutput);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void tablesFinished(Counts count) {
+ try {
+ FitProtocol.writeCounts(count, socketOutput);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+}
View
144 src/main/java/info/fitnesse/TransactionalSlimServer.java
@@ -0,0 +1,144 @@
+package info.fitnesse;
+
+import fitnesse.socketservice.SocketServer;
+import fitnesse.socketservice.SocketService;
+import fitnesse.util.StreamReader;
+
+import info.fitnesse.TransactionalFitServer.DocumentRunner;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.net.Socket;
+import java.util.List;
+import fitnesse.slim.*;
+public class TransactionalSlimServer implements SocketServer {
+ private StreamReader reader;
+ private BufferedWriter writer;
+ private ListExecutor executor;
+ public static final String EXCEPTION_TAG = "__EXCEPTION__:";
+ private boolean verbose;
+ private RollbackIntf rollbackProcessingBean;
+ public TransactionalSlimServer() {
+ this(false);
+ }
+
+ public TransactionalSlimServer(boolean verbose) {
+ this.verbose = verbose;
+ }
+
+ public void serve(Socket s) {
+ try{
+ rollbackProcessingBean = FitnesseSpringContext.getRollbackBean();
+ }
+ catch (Throwable e){
+ e.printStackTrace();
+ closeEnclosingServiceInSeperateThread();
+ }
+ try {
+ tryProcessInstructions(s);
+ } catch (Throwable e) {
+ e.printStackTrace();
+ } finally {
+ close();
+ closeEnclosingServiceInSeperateThread();
+ }
+ }
+
+ private void closeEnclosingServiceInSeperateThread() {
+ new Thread(new Runnable() {
+ public void run() {
+ try {
+ TransactionalSlimService.instance.close();
+ } catch (Exception e) {
+
+ }
+ }
+ }
+ ).start();
+ }
+
+ private void tryProcessInstructions(Socket s) throws Exception {
+ initialize(s);
+ boolean more = true;
+ while (more){
+ if (verbose) System.err.println("processing instruction set");
+ more = processOneSetOfInstructions();
+ if (verbose) System.err.println("instruction set processed");
+ }
+ }
+
+ private void initialize(Socket s) throws IOException {
+ executor = new ListExecutor(verbose);
+ reader = new StreamReader(s.getInputStream());
+ writer = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
+ writer.write(String.format("Slim -- %s\n", SlimVersion.VERSION));
+ writer.flush();
+ }
+
+ private boolean processOneSetOfInstructions() throws Exception {
+ String instructions = getInstructionsFromClient();
+ if (instructions != null) {
+ return processTheInstructions(instructions);
+ }
+ return true;
+ }
+
+ private class InstructionRunner implements Runnable{
+ private String instructions;
+ public InstructionRunner(String instructions){
+ this.instructions=instructions;
+ }
+ public void run() {
+ List<Object> results = executeInstructions(instructions);
+ try{
+ sendResultsToClient(results);
+ }
+ catch(IOException iex){
+ throw new Error(iex);
+ }
+ }
+ }
+ private boolean processTheInstructions(String instructions) throws IOException {
+ if (instructions.equalsIgnoreCase("bye")) {
+ return false;
+ } else {
+// List<Object> results = executeInstructions(instructions);
+// sendResultsToClient(results);
+ try {
+ rollbackProcessingBean.process(new InstructionRunner(instructions));
+ } catch (RollbackNow rn) {
+ if (verbose) System.err.println("rolling back now" + "\n");
+ }
+ return true;
+ }
+ }
+
+ private String getInstructionsFromClient() throws Exception {
+ int instructionLength = Integer.parseInt(reader.read(6));
+ reader.read(1);
+ String instructions = reader.read(instructionLength);
+ return instructions;
+ }
+
+ private List<Object> executeInstructions(String instructions) {
+ List<Object> statements = ListDeserializer.deserialize(instructions);
+ List<Object> results = executor.execute(statements);
+ return results;
+ }
+
+ private void sendResultsToClient(List<Object> results) throws IOException {
+ String resultString = ListSerializer.serialize(results);
+ writer.write(String.format("%06d:%s", resultString.length(), resultString));
+ writer.flush();
+ }
+
+ private void close() {
+ try {
+ reader.close();
+ writer.close();
+ } catch (Exception e) {
+
+ }
+ }
+}
View
42 src/main/java/info/fitnesse/TransactionalSlimService.java
@@ -0,0 +1,42 @@
+package info.fitnesse;
+import fitnesse.components.CommandLine;
+import fitnesse.slim.SlimServer;
+import fitnesse.slim.SlimService;
+import fitnesse.socketservice.SocketService;
+
+import java.util.Arrays;
+
+public class TransactionalSlimService extends SocketService {
+ public static boolean verbose;
+ public static int port;
+
+ public static void main(String[] args) throws Exception {
+ if (parseCommandLine(args)) {
+ new TransactionalSlimService(port, verbose);
+ } else {
+ System.err.println("Invalid command line arguments:" + Arrays.asList(args));
+ }
+ }
+
+ static boolean parseCommandLine(String[] args) {
+ CommandLine commandLine = new CommandLine("[-v] port");
+ if (commandLine.parse(args)) {
+ verbose = commandLine.hasOption("v");
+ String portString = commandLine.getArgument("port");
+ port = Integer.parseInt(portString);
+ return true;
+ }
+ return false;
+ }
+
+ public TransactionalSlimService(int port) throws Exception {
+ this(port, false);
+ }
+
+ public static TransactionalSlimService instance;
+ public TransactionalSlimService(int port, boolean verbose) throws Exception {
+// super(port, new TransactionalSlimServer(verbose));
+ super(port, new TransactionalSlimServer(verbose));
+ instance=this;
+ }
+}
View
15 src/main/resources/embedded-rollback.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:p="http://www.springframework.org/schema/p"
+ xmlns:tx="http://www.springframework.org/schema/tx"
+ xmlns:aop="http://www.springframework.org/schema/aop"
+ xmlns:context="http://www.springframework.org/schema/context"
+ xsi:schemaLocation="
+ http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
+ http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
+ http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
+ http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"
+ >
+ <bean id="rollbackBean" class="info.fitnesse.RollbackBean" />
+</beans>
View
20 src/test/java/info/fitnesse/JDBCTestRepository.java
@@ -0,0 +1,20 @@
+package info.fitnesse;
+
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.transaction.annotation.Transactional;
+
+public class JDBCTestRepository implements TestRepository{
+ private JdbcTemplate template;
+ public JDBCTestRepository(JdbcTemplate template){
+ this.template=template;
+ }
+ @Transactional
+ public void put(String value){
+ template.update("insert into test(name) values (?)",new Object[]{value});
+ }
+ @Transactional
+ public int check (String value){
+ return template.queryForInt("select count(*) from test where name=?",new Object[]{value});
+ }
+
+}
View
10 src/test/java/info/fitnesse/TestRepository.java
@@ -0,0 +1,10 @@
+package info.fitnesse;
+
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.transaction.annotation.Transactional;
+
+public interface TestRepository {
+
+ public void put(String value);;
+ public int check (String value);
+}
View
27 src/test/java/info/fitnesse/TestTxRollback.java
@@ -0,0 +1,27 @@
+package info.fitnesse;
+
+import junit.framework.Assert;
+
+import org.junit.Test;
+
+public class TestTxRollback {
+ @Test
+ public void testRollsBack(){
+ System.setProperty("spring.context", "classpath:spring.xml");
+ RollbackIntf bn= FitnesseSpringContext.getRollbackBean();
+ final TestRepository tr=(TestRepository) FitnesseSpringContext.getInstance().getBean("testRepository");
+ try {
+ bn.process(new Runnable(){
+ public void run() {
+ tr.put("test123");
+ Assert.assertEquals(1,tr.check("test123"));
+ }
+ });
+ Assert.fail("exception expected");
+ }
+ catch (RollbackNow rn){
+ //
+ }
+ Assert.assertEquals(0,tr.check("test123"));
+ }
+}
View
31 src/test/resources/spring.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:p="http://www.springframework.org/schema/p"
+ xmlns:tx="http://www.springframework.org/schema/tx"
+ xmlns:aop="http://www.springframework.org/schema/aop"
+ xmlns:context="http://www.springframework.org/schema/context"
+ xsi:schemaLocation="
+ http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
+ http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
+ http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
+ http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"
+ default-autowire="byType">
+
+
+ <bean id="dataSource" destroy-method="close" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
+ <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
+ <property name="url" value="jdbc:mysql://localhost/test"/>
+ <property name="username" value="root"/>
+ </bean>
+ <bean id="jdbcTemplate"
+ class="org.springframework.jdbc.core.JdbcTemplate">
+ <property name="dataSource" ref="dataSource"/>
+ </bean>
+ <bean id="testRepository" class="info.fitnesse.JDBCTestRepository" autowire="constructor"/>
+ <tx:annotation-driven transaction-manager="jdbcTransactionManager"/>
+ <bean id="jdbcTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
+ <property name="dataSource" ref="dataSource"/>
+ </bean>
+ <bean id="rollbackBean2" class="info.fitnesse.RollbackBean" />
+</beans>

0 comments on commit be039cd

Please sign in to comment.