diff --git a/Language/Java/Parser.hs b/Language/Java/Parser.hs
--- a/Language/Java/Parser.hs
+++ b/Language/Java/Parser.hs
@@ -81,6 +81,7 @@
     mpd <- opt packageDecl
     ids <- list importDecl
     tds <- list typeDecl
+    eof
     return $ CompilationUnit mpd ids (catMaybes tds)
 
 packageDecl :: P PackageDecl
diff --git a/language-java.cabal b/language-java.cabal
--- a/language-java.cabal
+++ b/language-java.cabal
@@ -1,5 +1,5 @@
 Name:                   language-java
-Version:                0.2.2
+Version:                0.2.3
 License:                BSD3
 License-File:           LICENSE
 Author:                 Niklas Broberg
@@ -10,15 +10,12 @@
 Homepage:               http://github.com/vincenthz/language-java
 Stability:              Experimental
 Build-Type:             Simple
-Cabal-Version:          >= 1.6
+Cabal-Version:          >= 1.8
 
 Extra-Source-Files:
-			test/abstract.java
-			test/rawTypes.java
-			test/test.java
-			test/typeVarMembers.java
-			test/LexerTest.hs
-			Language/Java/Lexer.x
+    tests/java/good/*.java
+    tests/java/bad/*.java
+    Language/Java/Lexer.x
 
 source-repository head
   type:                 git
@@ -41,3 +38,19 @@
                         Language.Java.Pretty
   
   Other-modules:	
+
+Test-Suite test-java-parse
+  type:              exitcode-stdio-1.0
+  hs-source-dirs:    tests
+  Main-is:           Tests.hs
+  Build-Depends:     base >= 3 && < 5
+                   , mtl
+                   , QuickCheck >= 2
+                   , HUnit
+                   , test-framework
+                   , test-framework-quickcheck2
+                   , test-framework-hunit
+                   , language-java
+                   , filepath
+                   , directory
+  ghc-options:       -Wall -fno-warn-orphans -fno-warn-missing-signatures
diff --git a/test/LexerTest.hs b/test/LexerTest.hs
deleted file mode 100644
--- a/test/LexerTest.hs
+++ /dev/null
@@ -1,60 +0,0 @@
-module Main where
-
-import Test.HUnit
-import Text.Printf (printf)
-import Language.Java.Lexer
-
-import qualified LexerQCTest as LQCT
-
-data LexerTest = LexerTest
-                    String  -- given input
-                    [Token] -- expected result
-
-lexerTests :: [LexerTest]
-lexerTests = concat [
-    integerTests 0
-  , integerTests 1
-  , integerTests 23
-  , integerTests 52
-  , [ LexerTest "\"\\\\\" \"\""
-      [StringTok "\\", StringTok ""] ]
-  , blockCommentTests
-  ]
-
--- | Generates integer lexing tests using decimal, octal, and
--- hexadecimal representations of @int@ and @long@ literals.
-integerTests :: Integer -> [LexerTest]
-integerTests expectedValue =
-  concat [ intAndLongs decimalRep,
-           intAndLongs octalRep,
-           intAndLongs hexRep ]
-  where intAndLongs rep = [ LexerTest rep [intToken],
-                            LexerTest (rep ++ "l") [longToken],
-                            LexerTest (rep ++ "L") [longToken] ]
-        decimalRep = printf "%d"   expectedValue
-        octalRep   = printf "0%o"  expectedValue
-        hexRep     = printf "0x%x" expectedValue
-        intToken   = IntTok  expectedValue
-        longToken  = LongTok expectedValue
-  
-blockCommentTests :: [LexerTest]
-blockCommentTests =
-  map mkTest [ "",
-               "*",
-               "**",
-               "***",
-               "** / ***",
-               "*\n/ ***\n /**"]
-  where mkTest commentBody =
-          LexerTest (comment ++ "class" ++ comment) [KW_Class]
-            where comment = "/*" ++ commentBody ++ "*/"
-                  
-main :: IO ()
-main = do
-  _ <- runTestTT $ TestList $ map lexerTestToTest lexerTests
-  LQCT.run
-  return ();
-  where
-    lexerTestToTest (LexerTest input expected) = TestCase $ do
-      let result = [ x | L _ x <- lexer input ]
-      assertEqual input expected result
diff --git a/test/abstract.java b/test/abstract.java
deleted file mode 100644
--- a/test/abstract.java
+++ /dev/null
@@ -1,16 +0,0 @@
-abstract class Point {
-	int x = 1, y = 1;
-	void move(int dx, int dy) {
-		x += dx;
-		y += dy;
-		alert();
-	}
-	abstract void alert();
-}
-abstract class ColoredPoint extends Point {
-	int color;
-}
-
-class SimplePoint extends Point {
-	void alert() { }
-}
diff --git a/test/rawTypes.java b/test/rawTypes.java
deleted file mode 100644
--- a/test/rawTypes.java
+++ /dev/null
@@ -1,16 +0,0 @@
-import java.util.*;
-
-class NonGeneric {
-
-    Collection<Number> myNumbers(){return null;}
-}
-abstract class RawMembers<T> extends NonGeneric implements Collection<String> {
-    static Collection<NonGeneric> cng = new ArrayList<NonGeneric>();
-
-    public static void main(String[] args) {
-                RawMembers rw = null;
-                Collection<Number> cn = rw.myNumbers(); // ok
-                Iterator<String> is = rw.iterator(); // unchecked warning
-                Collection<NonGeneric> cnn = rw.cng; // ok - static member
-    }
-}
diff --git a/test/test.java b/test/test.java
deleted file mode 100644
--- a/test/test.java
+++ /dev/null
@@ -1,2 +0,0 @@
-package testPackage;
-class Other { static String hello = "Hello"; }
diff --git a/test/typeVarMembers.java b/test/typeVarMembers.java
deleted file mode 100644
--- a/test/typeVarMembers.java
+++ /dev/null
@@ -1,13 +0,0 @@
- package TypeVarMembers;
-
-         class C {
-                 void mCDefault() {}
-                 public void mCPublic() {}
-                 private void mCPrivate() {}
-                 protected void mCProtected() {}
-         }
-         class CT extends C implements I {}
-         interface I {
-                 void mI();
-                 <T extends C & I> void test(T t) ;
-        }
diff --git a/tests/Tests.hs b/tests/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Tests.hs
@@ -0,0 +1,74 @@
+{-# LANGUAGE ScopedTypeVariables #-}
+module Main where
+
+import Test.Framework (defaultMain, testGroup)
+import Test.Framework.Providers.QuickCheck2 (testProperty)
+import Test.Framework.Providers.HUnit (testCase)
+import Test.HUnit (assertBool)
+
+import Test.QuickCheck
+import Test.QuickCheck.Test
+
+import System.Directory
+import System.FilePath
+
+import Control.Applicative
+import Control.Monad
+import Data.List (isSuffixOf)
+
+import Language.Java.Parser
+import Language.Java.Syntax
+import Language.Java.Pretty
+import qualified Control.Exception as E
+
+instance Arbitrary CompilationUnit where
+    arbitrary = CompilationUnit <$> arbitrary <*> arbitrary <*> ((:[]) <$> arbitrary)
+instance Arbitrary PackageDecl where
+    arbitrary = PackageDecl <$> arbitrary
+instance Arbitrary ImportDecl where
+    arbitrary = ImportDecl <$> arbitrary <*> arbitrary <*> arbitrary
+instance Arbitrary TypeDecl where
+    arbitrary = ClassTypeDecl <$> arbitrary
+instance Arbitrary ClassDecl where
+    arbitrary = ClassDecl <$> pure [] <*> arbitrary <*> pure [] <*> pure Nothing <*> pure [] <*> arbitrary
+instance Arbitrary ClassBody where
+    arbitrary = ClassBody <$> pure []
+instance Arbitrary Name where
+    arbitrary = Name <$> (choose (1,3) >>= \len -> replicateM len arbitrary)
+instance Arbitrary Ident where
+    arbitrary = Ident . unkeyword <$> (choose (1,15) >>= \len -> replicateM len (elements (['a'..'z'] ++ ['A'..'Z'])))
+      where unkeyword k
+                | k `elem` ["if","do","then","else"] = "x" ++ k
+                | otherwise                          = k
+
+----------------------------------------------------------
+testJavaDirectory :: FilePath
+testJavaDirectory = "tests" </> "java"
+
+isJavaFile :: FilePath -> Bool
+isJavaFile f = ".java" `isSuffixOf` f
+
+toTestCase expected jFile = testCase (takeBaseName jFile) doTest
+  where doTest = do r <- E.try parseOne
+                    case r of
+                        Left (e :: E.SomeException) -> assertBool ("failure exception: " ++ show e) (not expected)
+                        Right (Left perr)           -> assertBool ("failure parse error: " ++ show perr) (not expected)
+                        Right (Right p)             -> assertBool ("success: " ++ show p) expected
+        parseOne = parser compilationUnit <$> readFile jFile
+
+getAllJavaPaths path = map (path </>) . filter isJavaFile <$> getDirectoryContents path
+
+main = do
+    exists <- doesDirectoryExist testJavaDirectory
+    when (not exists) $ error "cannot find tests files java directory"
+    
+    allGoodJavas <- getAllJavaPaths (testJavaDirectory </> "good")
+    allBadJavas <- getAllJavaPaths (testJavaDirectory </> "bad")
+
+    defaultMain
+        [ testGroup "parsing unit good" (map (toTestCase True) allGoodJavas)
+        , testGroup "parsing unit bad" (map (toTestCase False) allBadJavas)
+        , testProperty "parsing.generating==id" (\g -> case parser compilationUnit (show $ pretty g) of
+                                                            Right g'  -> g == g'
+                                                            Left perr -> error (show (pretty g) ++ show perr))
+        ]
diff --git a/tests/java/bad/empty.java b/tests/java/bad/empty.java
new file mode 100644
--- /dev/null
+++ b/tests/java/bad/empty.java
@@ -0,0 +1,1 @@
+for )(;
diff --git a/tests/java/bad/syntax.java b/tests/java/bad/syntax.java
new file mode 100644
--- /dev/null
+++ b/tests/java/bad/syntax.java
@@ -0,0 +1,2 @@
+module Abc where
+
diff --git a/tests/java/good/abstract.java b/tests/java/good/abstract.java
new file mode 100644
--- /dev/null
+++ b/tests/java/good/abstract.java
@@ -0,0 +1,16 @@
+abstract class Point {
+	int x = 1, y = 1;
+	void move(int dx, int dy) {
+		x += dx;
+		y += dy;
+		alert();
+	}
+	abstract void alert();
+}
+abstract class ColoredPoint extends Point {
+	int color;
+}
+
+class SimplePoint extends Point {
+	void alert() { }
+}
diff --git a/tests/java/good/miscMath.java b/tests/java/good/miscMath.java
new file mode 100644
--- /dev/null
+++ b/tests/java/good/miscMath.java
@@ -0,0 +1,33 @@
+import java.util.Random;
+class MiscMath<T extends Number>{
+	int divisor;
+	MiscMath(int divisor) {
+		this.divisor = divisor;
+	}
+        float ratio(long l) {
+                try {
+                        l /= divisor;
+                } catch (Exception e) {
+                        if (e instanceof ArithmeticException)
+                                l = Long.MAX_VALUE;
+                        else
+                                l = 0;
+                }
+                return (float)l;
+        }
+        double gausser() {
+                Random r = new Random();
+                double[] val = new double[2];
+                val[0] = r.nextGaussian();
+                val[1] = r.nextGaussian();
+                return (val[0] + val[1]) / 2;
+	}
+	Collection<Number> fromArray(Number[] na) {
+		Collection<Number> cn = new ArrayList<Number>();
+		for (Number n : na) {
+			cn.add(n);
+		}
+		return cn;
+	}
+	void <S> loop(S s){ this.<S>loop(s);}
+}
diff --git a/tests/java/good/miscMath2.java b/tests/java/good/miscMath2.java
new file mode 100644
--- /dev/null
+++ b/tests/java/good/miscMath2.java
@@ -0,0 +1,10 @@
+import java.util.Random;
+class MiscMath<T extends Number>{
+	Collection<Number> fromArray(Number[] na) {
+		Collection<Number> cn = new ArrayList<Number>();
+		for (Number n : na) {
+			cn.add(n)
+		}
+		return cn;
+	}
+}
diff --git a/tests/java/good/miscMath3.java b/tests/java/good/miscMath3.java
new file mode 100644
--- /dev/null
+++ b/tests/java/good/miscMath3.java
@@ -0,0 +1,4 @@
+import java.util.Random;
+class MiscMath<T extends Number>{
+	void <S> loop(S s){ this.<S>loop(s);}
+}
diff --git a/tests/java/good/miscMath4.java b/tests/java/good/miscMath4.java
new file mode 100644
--- /dev/null
+++ b/tests/java/good/miscMath4.java
@@ -0,0 +1,9 @@
+import java.util.Random;
+class MiscMath<T extends Number>{
+	Collection<Number> fromArray(Number[] na) {
+		for (Number n : na) {
+			cn.add(n)
+		}
+		Collection<Number> cn = new ArrayList<Number>();
+	}
+}
diff --git a/tests/java/good/miscMath5.java b/tests/java/good/miscMath5.java
new file mode 100644
--- /dev/null
+++ b/tests/java/good/miscMath5.java
@@ -0,0 +1,9 @@
+import java.util.Random;
+class MiscMath<T extends Number>{
+	Collection<Number> fromArray(Number[] na) {
+		for (Number n : na) {
+			cn.add(n);
+		}
+		Collection<Number> cn = new ArrayList<Number>();
+	}
+}
diff --git a/tests/java/good/rawTypes.java b/tests/java/good/rawTypes.java
new file mode 100644
--- /dev/null
+++ b/tests/java/good/rawTypes.java
@@ -0,0 +1,16 @@
+import java.util.*;
+
+class NonGeneric {
+
+    Collection<Number> myNumbers(){return null;}
+}
+abstract class RawMembers<T> extends NonGeneric implements Collection<String> {
+    static Collection<NonGeneric> cng = new ArrayList<NonGeneric>();
+
+    public static void main(String[] args) {
+                RawMembers rw = null;
+                Collection<Number> cn = rw.myNumbers(); // ok
+                Iterator<String> is = rw.iterator(); // unchecked warning
+                Collection<NonGeneric> cnn = rw.cng; // ok - static member
+    }
+}
diff --git a/tests/java/good/test.java b/tests/java/good/test.java
new file mode 100644
--- /dev/null
+++ b/tests/java/good/test.java
@@ -0,0 +1,2 @@
+package testPackage;
+class Other { static String hello = "Hello"; }
diff --git a/tests/java/good/typeVarMembers.java b/tests/java/good/typeVarMembers.java
new file mode 100644
--- /dev/null
+++ b/tests/java/good/typeVarMembers.java
@@ -0,0 +1,13 @@
+ package TypeVarMembers;
+
+         class C {
+                 void mCDefault() {}
+                 public void mCPublic() {}
+                 private void mCPrivate() {}
+                 protected void mCProtected() {}
+         }
+         class CT extends C implements I {}
+         interface I {
+                 void mI();
+                 <T extends C & I> void test(T t) ;
+        }
