diff options
author | Andreas Fitzek <andreas.fitzek@iaik.tugraz.at> | 2014-08-29 15:09:54 +0200 |
---|---|---|
committer | Andreas Fitzek <andreas.fitzek@iaik.tugraz.at> | 2014-08-29 15:09:54 +0200 |
commit | ffd1e0da6b73e2737f5cad0a6d3e82dbc3de206f (patch) | |
tree | 73a685d2c6becb3a274522fb31b898c8a9b0903c /pdf-as-tests | |
parent | 7a983c6687e8045efcc918c273bc43798319423b (diff) | |
download | pdf-as-4-ffd1e0da6b73e2737f5cad0a6d3e82dbc3de206f.tar.gz pdf-as-4-ffd1e0da6b73e2737f5cad0a6d3e82dbc3de206f.tar.bz2 pdf-as-4-ffd1e0da6b73e2737f5cad0a6d3e82dbc3de206f.zip |
Integrated PDF-AS Testing library
Diffstat (limited to 'pdf-as-tests')
24 files changed, 3320 insertions, 0 deletions
diff --git a/pdf-as-tests/build.gradle b/pdf-as-tests/build.gradle new file mode 100644 index 00000000..f2bce693 --- /dev/null +++ b/pdf-as-tests/build.gradle @@ -0,0 +1,147 @@ +apply plugin: 'java' +apply plugin: 'eclipse' + +jar { + manifest { + attributes 'Implementation-Title': 'PDF-AS-4 Test Library', 'JARMANIFEST': 'PDF-AS-LIB' + } +} + +repositories { + mavenLocal() + mavenCentral() + maven { url "http://anonsvn.icesoft.org/repo/maven2/releases/" } +} + +dependencies { + compile project (':pdf-as-lib') + compile project (':signature-standards:sigs-pkcs7detached') + compile project (':signature-standards:sigs-pades') + compile group: 'log4j', name: 'log4j', version: '1.2.17' + compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.5' + testCompile group: 'junit', name: 'junit', version: '4.+' + testCompile group: 'org.apache.pdfbox', name: 'preflight', version: '1.8.5' + testCompile group: 'org.apache.pdfbox', name: 'pdfbox', version: '1.8.6' + testCompile group: 'org.icepdf', name: 'icepdf-core', version: '5.0.7' +} + +logger.info("Building tasks for Test suites") +def suiteDir = new File(projectDir, "src/test/test-suites") +logger.info("Searching: " + suiteDir.absolutePath) + +suiteDir.eachDir { File subDir -> + def dirname = subDir.name + logger.info("Test Suite " + subDir.name + " found in " + subDir.absolutePath) + + task "runTestSuite${dirname.capitalize()}"(type: Test) { + description "runs tests from Test Suite: " + dirname + systemProperties 'test.dir': subDir.absolutePath + include '**/ParameterizedSignatureTestSuite.class' + + beforeSuite { TestDescriptor descriptor -> + if(descriptor.getParent() == null) { + logger.quiet("++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); + } + if(descriptor.getClassName().equals("at.gv.egiz.param_tests.ParameterizedSignatureTestSuite")) { + logger.quiet("Starting suite: " + dirname + " (" + descriptor.getName() + ")") + } + } + + afterSuite { TestDescriptor descriptor, TestResult result -> + if(descriptor.getClassName().equals("at.gv.egiz.param_tests.ParameterizedSignatureTestSuite")) { + logger.quiet("------------------"); + logger.quiet("Ending suite: " + dirname); + logger.quiet("\tResult (SUCCESS/ERROR/SKIPPED/TOTAL): " + + result.getSuccessfulTestCount() + "/" + + result.getFailedTestCount() + "/" + + result.getSkippedTestCount() + "/" + + result.getTestCount()); + float duration_ms = result.getEndTime() - result.getStartTime() + float duration_sec = duration_ms / 1000.0f + logger.quiet("\tDuration: " + duration_sec + " s [" + duration_ms + " ms]") + logger.quiet("\tReport @ file://" + subDir.absolutePath + "/index.html") + } + if(descriptor.getParent() == null) { + logger.quiet("++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); + } + } + + beforeTest { TestDescriptor descriptor -> + logger.quiet("------------------"); + logger.info("Running test: " + descriptor.getName()) + } + + afterTest { TestDescriptor descriptor, TestResult result -> + float duration_ms = result.getEndTime() - result.getStartTime() + float duration_sec = duration_ms / 1000.0f + logger.quiet(result.getResultType().toString() + " => [" + + descriptor.getName() + "] took " + duration_sec + " s [" + duration_ms + " ms]") + if(TestResult.ResultType.FAILURE.equals(result.getResultType())) { + if(result.getException() != null) { + logger.error("Failed test: " + result.getException().getMessage()); + result.getException().printStackTrace(); + } else { + logger.error("Failed test provided no exception"); + } + } + } + } + + test.dependsOn tasks.getByPath("runTestSuite${dirname.capitalize()}") + + + task "cleanTestSuite${dirname.capitalize()}"(type: Delete) { + outputs.upToDateWhen { false } + delete fileTree (dir: subDir.absolutePath, include: "index.html") + delete fileTree (dir: subDir.absolutePath, include: "**/test_result.html") + } + + task "cleanOutFolders${dirname.capitalize()}"() << { + subDir.eachDir { File tcDir -> + File outDir = new File(tcDir, "out"); + if(outDir.exists() && outDir.isDirectory()) { + outDir.eachFileRecurse { File mfile -> + mfile.delete() + } + outDir.delete() + } + } + } + + clean.dependsOn tasks.getByPath("cleanOutFolders${dirname.capitalize()}") + clean.dependsOn tasks.getByPath("cleanTestSuite${dirname.capitalize()}") +} + +tasks.getByPath(":pdf-as-lib:test").dependsOn test + +test { + include '**/DummyTest.class' + + /*beforeSuite { TestDescriptor descriptor -> + logger.quiet("++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); + logger.quiet("Starting suite: " + descriptor.getName()) + } + + afterSuite { TestDescriptor descriptor, TestResult result -> + logger.quiet("Ending suite: " + descriptor.getName()); + logger.quiet("\tResult (SUCCESS/ERROR/SKIPPED/TOTAL): " + + result.getSuccessfulTestCount() + "/" + + result.getFailedTestCount() + "/" + + result.getSkippedTestCount() + "/" + + result.getTestCount()); + logger.quiet("\tDuration: " + (result.getEndTime() - result.getStartTime()) + " MS"); + if(descriptor.getClassName().equals("at.gv.egiz.param_tests.ParameterizedSignatureTestSuite")) { + logger.quiet("\tReport @ file:///" + subDir.absolutePath + "/index.html") + } + logger.quiet("++++++++++++++++++++++++++++++++++++++++++++++++++++++++"); + }*/ + + beforeTest { TestDescriptor descriptor -> + logger.quiet("------------------"); + logger.quiet("Running test: " + descriptor.getName()) + } + + afterTest { TestDescriptor descriptor, TestResult result -> + logger.quiet("Ending test: " + descriptor.getName() + " result " + result) + } +} diff --git a/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/PDFASignatureTest.java b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/PDFASignatureTest.java new file mode 100644 index 00000000..e5c8e1be --- /dev/null +++ b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/PDFASignatureTest.java @@ -0,0 +1,166 @@ +package at.gv.egiz.param_tests; + +import static org.junit.Assert.assertTrue; + +import java.io.Closeable; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.security.cert.CertificateException; +import java.util.Collection; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.pdfbox.preflight.PreflightDocument; +import org.apache.pdfbox.preflight.ValidationResult; +import org.apache.pdfbox.preflight.exception.SyntaxValidationException; +import org.apache.pdfbox.preflight.parser.PreflightParser; +import org.junit.Assume; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import at.gv.egiz.param_tests.provider.BaseSignatureTestData; +import at.gv.egiz.param_tests.provider.PDFAProvider; +import at.gv.egiz.param_tests.serialization.SerializiationManager; +import at.gv.egiz.param_tests.serialization.html.PDFAHTMLSerizalier; +import at.gv.egiz.param_tests.testinfo.PDFATestInfo; +import at.gv.egiz.pdfas.common.exceptions.PdfAsException; + +/** + * Parameterized unit-test for checking if a PDF-file is PDFA conform after + * signing. The test result is inconclusive if the file is already not conform + * before signing. + * + * @author mtappler + * + */ +@RunWith(Parameterized.class) +public class PDFASignatureTest extends SignatureTest { + + /** + * the logger for this class + */ + private static final Logger logger = LoggerFactory + .getLogger(PDFASignatureTest.class); + + /** + * Sets up this class, which includes registering a serializer for it, + * respectively the test type. + */ + @BeforeClass + public static void setUpClass() { + SerializiationManager.getInstance().registerSerializer( + new PDFAHTMLSerizalier(), PDFATestInfo.class); + } + + /** + * Constructor which sets the parameter for this parameterized unit test. + * + * @param testDirectory + * name of the directory of this test + * @param testName + * the name of this test + * @param testData + * basic test data, like connector, or signature profile + */ + public PDFASignatureTest(String testDirectory, String testName, + BaseSignatureTestData testData) { + this.baseTestData = testData; + } + + /** + * Static data-function, which is needed for JUnit's parameterized tests. It + * returns one collection item per test, which contains one array element + * per constructor parameter. + * + * @return the parameterized test data + */ + //@Parameters(name = "{index}-pdfa signature test:<{0}> - {1}") + @Parameters(name = "{index}-{1}") + public static Collection<Object[]> data() { + return new PDFAProvider().gatherData(); + } + + /** + * The actual unit test which is executed, it checks if the input is + * PDFA-conform, signs the PDF file and checks if the signing result is + * PDFA-conform. If the input file is not PDF-conform the test is skipped as + * being inconclusive. + * + * @throws CertificateException + * @throws FileNotFoundException + * @throws IOException + * @throws PdfAsException + */ + @Test + public void pdfaTest() throws CertificateException, FileNotFoundException, + IOException, PdfAsException { + PDFATestInfo testInfo = SerializiationManager.getInstance() + .createTestInfo(PDFATestInfo.class, baseTestData); + assertTrue("No input file given", baseTestData.getPdfFile() != null); + File inputFile = new File(baseTestData.getPdfFile()); + assertTrue("Given input file must exists", inputFile.exists()); + Pair<ValidationResult, Throwable> resultBeforeSign = checkPDFAConformance(inputFile); + testInfo.setResultBeforeSign(resultBeforeSign); + Assume.assumeTrue("Input should conform to PDF-A standard", + resultBeforeSign.getLeft() != null + && resultBeforeSign.getLeft().isValid()); + File outputPdfFile = signPDFFile(); + logger.debug("Signed document " + baseTestData.getOutputFile()); + + Pair<ValidationResult, Throwable> resultAfterSign = checkPDFAConformance(outputPdfFile); + testInfo.setResultAfterSign(resultAfterSign); + assertTrue("Output file must be PDF-A conform", + resultAfterSign.getLeft() != null + && resultAfterSign.getLeft().isValid()); + } + + /** + * Helper method for checking PDFA-conformance. + * + * @param fd + * the File object corresponding to the file, which should be + * checked. + * @return a pair consisting of the validation result and an exception, + * which might have been thrown during the validation (both possibly + * null) + */ + private Pair<ValidationResult, Throwable> checkPDFAConformance(File fd) { + PreflightDocument document = null; + ValidationResult result = null; + try { + PreflightParser parser = new PreflightParser(fd); + parser.parse(); + document = parser.getPreflightDocument(); + document.validate(); + document.close(); + result = document.getResult(); + return new ImmutablePair<ValidationResult, Throwable>(result, null); + } catch (SyntaxValidationException e) { + logger.debug("The file " + fd.getName() + + " is syntactically invalid.", e); + return new ImmutablePair<ValidationResult, Throwable>(result, e); + } catch (IOException e) { + logger.debug("An IOException (" + e.getMessage() + + ") occurred, while validating the PDF-A conformance of " + + fd.getName(), e); + return new ImmutablePair<ValidationResult, Throwable>(result, e); + } catch (RuntimeException e) { + logger.debug("An RuntimeException (" + e.getMessage() + + ") occurred, while validating the PDF-A conformance of " + + fd.getName(), e); + return new ImmutablePair<ValidationResult, Throwable>(result, e); + } finally { + if (document != null) { + IOUtils.closeQuietly((Closeable)document); + } + } + } + +} diff --git a/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/ParameterizedSignatureTestSuite.java b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/ParameterizedSignatureTestSuite.java new file mode 100644 index 00000000..a8e03b5e --- /dev/null +++ b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/ParameterizedSignatureTestSuite.java @@ -0,0 +1,137 @@ +package at.gv.egiz.param_tests; + +import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.PrintStream; +import java.net.URL; + +import org.apache.commons.io.output.TeeOutputStream; +import org.apache.log4j.PropertyConfigurator; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.runner.Description; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; +import org.junit.runners.model.InitializationError; + +import at.gv.egiz.param_tests.serialization.SerializiationManager; +import at.gv.egiz.param_tests.serialization.html.HTMLTestSummaryWriter; + +/** + * Test suite which groups all available parameterized tests in + * <code>Suite.SuiteClasses</code>-annotation. + * + * @author mtappler + * + */ +@RunWith(Suite.class) +@Suite.SuiteClasses({ PDFASignatureTest.class, SignaturePositionTest.class }) +public class ParameterizedSignatureTestSuite { + /** + * variable to save the standard output <code>PrintStream</code> after + * redirecting it, to be able to reset it afterwards, this should only be + * necessary if this class is loaded and run dynamically (not using right + * mouse -> Run As -> JUnit Test) + */ + private static PrintStream stdOutSave; + /** + * variable to save the standard error <code>PrintStream</code> after + * redirecting it + */ + private static PrintStream stderrSave; + /** + * stream which logs everything which is written to stdout during test + * execution + */ + private static ByteArrayOutputStream stdoutLog; + /** + * stream which logs everything which is written to stderr during test + * execution + */ + private static ByteArrayOutputStream stderrLog; + + /** + * the location of the log4j configuration + */ + private static final URL log4j = ParameterizedSignatureTestSuite.class + .getResource("/log4j.properties"); + /** + * If set to true, logging will be configured, when this class is loaded. + * This ensures that logging will be available in tests before logging is + * configured by pdf-as. + */ + private static final boolean CONFIGURE_LOGGING = true; + + /** + * Getter + * + * @return the byte array output stream which captures stdout + */ + public static ByteArrayOutputStream getStdoutLog() { + // to avoid null NPE, if a test is started without testsuite + return stdoutLog != null ? stdoutLog : new ByteArrayOutputStream(); + } + + /** + * Getter + * + * @return the byte array output stream which captures stderr + */ + public static ByteArrayOutputStream getStderrLog() { + // to avoid null NPE + return stderrLog != null ? stderrLog : new ByteArrayOutputStream(); + } + + static { + String testDir = System.getProperty("test.dir"); + System.out.println("Running Tests from: " + testDir); + } + + /** + * Sets up the test run by redirecting stdout and stderr such that both are + * written to the console and to a byte array output stream and configures + * logging if the corresponding flag is set. + * + * @throws InitializationError + * if no test directory is given via the system property + * "test.dir" + */ + @BeforeClass + public static void setUpClass() throws InitializationError { + stdoutLog = new ByteArrayOutputStream(); + TeeOutputStream teeStdOut = new TeeOutputStream(System.out, stdoutLog); + stdOutSave = System.out; + System.setOut(new PrintStream(teeStdOut)); + stderrLog = new ByteArrayOutputStream(); + TeeOutputStream teeStdErr = new TeeOutputStream(System.err, stderrLog); + stderrSave = System.err; + System.setErr(new PrintStream(teeStdErr)); + if (CONFIGURE_LOGGING && log4j != null) { + try { + PropertyConfigurator.configure(new FileInputStream(log4j + .getFile())); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + } + + String testDir = System.getProperty("test.dir"); + if (testDir == null) + throw new InitializationError("Test directory is not set"); + + SerializiationManager.getInstance().setTestSummaryWriter( + new HTMLTestSummaryWriter(testDir)); + } + + /** + * Resets stdout and stderr redirection and serializes all test results. + */ + @AfterClass + public static void tearDownClass() { + System.setOut(stdOutSave); + System.setErr(stderrSave); + SerializiationManager.getInstance().serializeAll(); + } + +} diff --git a/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/SignaturePositionTest.java b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/SignaturePositionTest.java new file mode 100644 index 00000000..282eeb05 --- /dev/null +++ b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/SignaturePositionTest.java @@ -0,0 +1,337 @@ +package at.gv.egiz.param_tests; + +import static org.junit.Assert.*; + +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Rectangle; +import java.awt.image.BufferedImage; +import java.awt.print.PrinterException; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.security.cert.CertificateException; +import java.util.Collection; +import java.util.List; + +import javax.imageio.ImageIO; + +import org.icepdf.core.pobjects.Document; +import org.icepdf.core.pobjects.PDimension; +import org.icepdf.core.pobjects.Page; +import org.icepdf.core.util.GraphicsRenderingHints; +import org.junit.Assume; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import at.gv.egiz.param_tests.provider.BaseSignatureTestData; +import at.gv.egiz.param_tests.provider.SignaturePositionProvider; +import at.gv.egiz.param_tests.serialization.SerializiationManager; +import at.gv.egiz.param_tests.serialization.html.SignaturePositionHTMLSerializer; +import at.gv.egiz.param_tests.testinfo.SignaturePositionTestInfo; +import at.gv.egiz.pdfas.common.exceptions.PdfAsException; + +/** + * Parameterized unit test for checking the correct positioning of the signature + * block, or for capturing reference images (if it is known that the current + * implementation is correct). + * + * @author mtappler + * + */ +@RunWith(Parameterized.class) +public class SignaturePositionTest extends SignatureTest { + + /** + * Zoom value which controls the resolution of the image taken for image + * comparison. It must be the same as for capturing the reference picture. + */ + private static final float ZOOM = 4; + /** + * The page number of the page, which shows the signature block. + */ + private int sigPageNumber = -1; + /** + * A list of rectangular areas, which will be ignored for image comparison + */ + private List<Rectangle> ignoredAreas = null; + /** + * the file name of the reference file for image comparison + */ + private String refImageFileName = null; + /** + * if set to true, a reference image will be captured, but no actual + * comparison will be performed + */ + private boolean captureReference = false; + + /** + * Set up method to perform, when the class is loaded, which registers a + * serializer for it. + */ + @BeforeClass + public static void setUpClass() { + SerializiationManager.getInstance().registerSerializer( + new SignaturePositionHTMLSerializer(), + SignaturePositionTestInfo.class); + } + + /** + * Constructor for a single parameterized unit test, constructor parameters + * are parameters for the test. + * + * @param testDirectory + * directory, which defines this test + * @param testName + * name of this test + * @param baseTestData + * basic test data common to all parameterized signature tests + * @param positionString + * string specifying the position of the signature block + * @param sigPageNumber + * number of the page, on which the signature block should be + * shown + * @param ignoredAreas + * areas, which are ignored during the image comparison + * @param refImageFileName + * file name of the reference image + * @param captureReference + * if set to true, a reference image will be captured, but no + * actual comparison will be performed + */ + public SignaturePositionTest(String testDirectory, String testName, + BaseSignatureTestData baseTestData, String positionString, + int sigPageNumber, List<Rectangle> ignoredAreas, + String refImageFileName, boolean captureReference) { + this.baseTestData = baseTestData; + this.positionString = positionString; + this.sigPageNumber = sigPageNumber; + this.ignoredAreas = ignoredAreas; + if (baseTestData.getPdfFile() != null) { // should not happen actually + this.refImageFileName = extractDirectoryString(baseTestData + .getPdfFile()) + refImageFileName; + } else { + this.refImageFileName = refImageFileName; + } + this.captureReference = captureReference; + } + + /** + * This methods truncates a file such that the base name of the given is cut + * off, i.e. it does something like Unix' dirname, but adds a "/" at the + * end. + * + * @param fileName + * the name of the file + * @return the directory name part of the file + */ + private String extractDirectoryString(String fileName) { + try { + return new File(fileName).getCanonicalFile().getParent() + "/"; + } catch (IOException e) { + // fall back + return fileName.substring(0, fileName.lastIndexOf('/') + 1); + } + } + + /** + * Static data-function, which is needed for JUnit's parameterized tests. It + * returns one collection item per test, which contains one array element + * per constructor parameter. + * + * @return the parameterized test data + */ + //@Parameters(name = "{index}-signature position test:<{0}> - {1}") + @Parameters(name = "{index}-{1}") + public static Collection<Object[]> data() { + return new SignaturePositionProvider().gatherData(); + } + + /** + * Helper method, which captures a reference image, i.e. an image of the + * page with page number specified by <code>sigPageNumber</code> of the + * PDF-file after signing. The captured file is saved, as well as a modified + * version of it. The modified version contains black rectangles for the + * ignored areas. + * + * @param testInfo + * a test info object, which is used to store the location of the + * reference image, as well as the location of the reference + * image with ignored areas + * @throws IOException + */ + private void captureReferenceImage(SignaturePositionTestInfo testInfo) + throws IOException { + String pdfName = baseTestData.getOutputFile(); + String referenceOutputFile = refImageFileName; + int pageNumber = sigPageNumber; + BufferedImage image = captureImage(pdfName, pageNumber); + ImageIO.write(image, "png", new File(referenceOutputFile)); + Graphics refImageGraphics = image.createGraphics(); + ignoreAreas(refImageGraphics, image.getHeight()); + String refImageIgnored = extractDirectoryString(baseTestData + .getOutputFile()) + "refImage_ignored.png"; + ImageIO.write(image, "png", new File(refImageIgnored)); + testInfo.setRefImageIgnored(refImageIgnored); + refImageGraphics.dispose(); + image.flush(); + } + + /** + * The actual test method, which captures an image of the signature block + * page of the signed PDFs and compares it pixel by pixel with the reference + * image. + * + * @throws FileNotFoundException + * @throws CertificateException + * @throws IOException + * @throws PdfAsException + * @throws IndexOutOfBoundsException + * @throws PrinterException + */ + @Test + public void signaturePositionTest() throws FileNotFoundException, + CertificateException, IOException, PdfAsException, + IndexOutOfBoundsException, PrinterException { + SignaturePositionTestInfo testInfo = SerializiationManager + .getInstance().createTestInfo(SignaturePositionTestInfo.class, + baseTestData); + testInfo.getAdditionParameters().setCaptureReferenceImage( + captureReference); + testInfo.getAdditionParameters().setPositionString(positionString); + testInfo.getAdditionParameters().setIgnoredAreas(ignoredAreas); + testInfo.getAdditionParameters().setRefImageFileName(refImageFileName); + testInfo.getAdditionParameters().setSigPageNumber(sigPageNumber); + + Assume.assumeNotNull(positionString); + Assume.assumeNotNull(refImageFileName); + Assume.assumeFalse("A page number must be specified", + sigPageNumber == -1); + signPDFFile(); + if (captureReference) { + captureReferenceImage(testInfo); + return; + } + BufferedImage sigPageImage = captureImage(baseTestData.getOutputFile(), + sigPageNumber); + assertNotNull("Could not get image of page", sigPageImage); + BufferedImage refImage = ImageIO.read(new File(refImageFileName)); + assertNotNull("Could not get reference image", sigPageImage); + + assertEquals("Width of image differs from reference", + refImage.getWidth(), sigPageImage.getWidth()); + assertEquals("Height of image differs from reference", + refImage.getHeight(), sigPageImage.getHeight()); + + Graphics sigPageGraphics = sigPageImage.getGraphics(); + Graphics refImageGraphics = refImage.createGraphics(); + int imageHeight = sigPageImage.getHeight(); + ignoreAreas(sigPageGraphics, imageHeight); + ignoreAreas(refImageGraphics, imageHeight); + + String refImageIgnored = extractDirectoryString(baseTestData + .getOutputFile()) + "refImage_ignored.png"; + ImageIO.write(refImage, "png", new File(refImageIgnored)); + testInfo.setRefImageIgnored(refImageIgnored); + String sigPageImageIgnored = extractDirectoryString(baseTestData + .getOutputFile()) + "sigPageImage_ignored.png"; + ImageIO.write(sigPageImage, "png", new File(sigPageImageIgnored)); + testInfo.setSigPageImageIgnored(sigPageImageIgnored); + + // now perform the pixel by pixel comparison + boolean same = true; + BufferedImage differenceImage = new BufferedImage(refImage.getWidth(), + refImage.getHeight(), refImage.getType()); + Graphics differenceGraphics = differenceImage.createGraphics(); + differenceGraphics.setColor(Color.WHITE); + differenceGraphics.fillRect(0, 0, differenceImage.getWidth(), + differenceImage.getHeight()); + for (int x = 0; x < refImage.getWidth(); x++) { + for (int y = 0; y < refImage.getHeight(); y++) { + // since both reference and signature page image are + // produced in the same (use same color model) and we + // want a pixel by pixel comparison, this should work + boolean samePixel = refImage.getRGB(x, y) == sigPageImage + .getRGB(x, y); + if (!samePixel) { + same = false; + differenceImage.setRGB(x, y, Color.RED.getRGB()); + } + } + } + String diffImage = extractDirectoryString(baseTestData.getOutputFile()) + + "difference.png"; + ImageIO.write(differenceImage, "png", new File(diffImage)); + testInfo.setDiffImage(diffImage); + + differenceGraphics.dispose(); + differenceImage.flush(); + sigPageGraphics.dispose(); + sigPageImage.flush(); + refImageGraphics.dispose(); + refImage.flush(); + assertTrue("Images must be the same", same); + } + + /** + * Helper method, which "clears" all areas specified by + * <code>ignoredAreas</code>. "Cleared" are colored in the background color + * of the image (black). + * + * @param graphics + * Graphics object corresponding to an image + * @param imageHeight + * the height of the image + */ + protected void ignoreAreas(Graphics graphics, int imageHeight) { + for (Rectangle r : ignoredAreas) { + int effectiveX = (int) (r.x * ZOOM); + // in awt 0 is at top, in PDF-AS 0 is at bottom + int effectiveY = imageHeight - (int) (r.y * ZOOM); + int effectiveWidth = (int) (r.width * ZOOM); + int effectiveHeight = (int) (r.height * ZOOM); + graphics.clearRect(effectiveX, effectiveY, effectiveWidth, + effectiveHeight); + } + } + + /** + * This method captures an image of a page of a PDF document. This is done + * using the rendering capabilities of ICEPDF. + * + * @param fileName + * the name of the PDF file + * @param pageNumber + * the page number which should be captured + * @return the captured image + */ + private BufferedImage captureImage(String fileName, int pageNumber) { + Document document = new Document(); + try { + document.setFile(fileName); + } catch (Exception e) { + document.dispose(); + fail(String + .format("Not possible to capture page %d of file %s, because of %s.", + pageNumber, fileName, e.getMessage())); + } + Page page = document.getPageTree().getPage(pageNumber - 1); + page.init(); + PDimension sz = page.getSize(Page.BOUNDARY_CROPBOX, 0, ZOOM); + + int pageWidth = (int) sz.getWidth(); + int pageHeight = (int) sz.getHeight(); + + BufferedImage image = new BufferedImage(pageWidth, pageHeight, + BufferedImage.TYPE_4BYTE_ABGR); + Graphics g = image.createGraphics(); + page.paint(g, GraphicsRenderingHints.PRINT, Page.BOUNDARY_CROPBOX, 0, + ZOOM); + document.dispose(); + return image; + } + +} diff --git a/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/SignatureTest.java b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/SignatureTest.java new file mode 100644 index 00000000..97e7e819 --- /dev/null +++ b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/SignatureTest.java @@ -0,0 +1,176 @@ +package at.gv.egiz.param_tests; + +import static org.junit.Assert.assertNotNull; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.security.cert.CertificateException; +import java.util.Map; +import java.util.UUID; + +import org.apache.commons.io.IOUtils; +import org.junit.Rule; +import org.junit.rules.TestRule; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import at.gv.egiz.param_tests.provider.BaseSignatureDataProvider; +import at.gv.egiz.param_tests.provider.BaseSignatureTestData; +import at.gv.egiz.pdfas.common.exceptions.PdfAsException; +import at.gv.egiz.pdfas.common.utils.StreamUtils; +import at.gv.egiz.pdfas.lib.api.ByteArrayDataSink; +import at.gv.egiz.pdfas.lib.api.ByteArrayDataSource; +import at.gv.egiz.pdfas.lib.api.Configuration; +import at.gv.egiz.pdfas.lib.api.DataSource; +import at.gv.egiz.pdfas.lib.api.PdfAs; +import at.gv.egiz.pdfas.lib.api.PdfAsFactory; +import at.gv.egiz.pdfas.lib.api.sign.IPlainSigner; +import at.gv.egiz.pdfas.lib.api.sign.SignParameter; +import at.gv.egiz.pdfas.sigs.pades.PAdESSigner; +import at.gv.egiz.pdfas.sigs.pades.PAdESSignerKeystore; +import at.gv.egiz.sl.util.BKUSLConnector; +import at.gv.egiz.sl.util.MOAConnector; + +/** + * Base class for parameterized signature tests, which provides fields and + * methods common to all parameterized signature test. + * + * @author mtappler + * + */ +public class SignatureTest { + + /** + * test data common to all test types, like connector type + */ + protected BaseSignatureTestData baseTestData; + /** + * positioning string which specifies the position of the signature block + */ + protected String positionString; + /** + * logger for this class + */ + private static final Logger logger = LoggerFactory + .getLogger(PDFASignatureTest.class); + + /** + * a JUnit TestWatcher, this object gets notified several times a + * subclass of this class is run as test + */ + @Rule + public TestRule watchman = new SignatureTestWatcher(); + + /** + * PdfAs used for signing and creating a configuration + */ + protected PdfAs pdfAs; + /** + * the sign parameter used for signing in the test + */ + protected SignParameter signParameter; + /** + * the connector used for signing in the test + */ + protected IPlainSigner slConnector; + + /** + * This method signs a file, all parameter like input file name are specified + * through fields of this class. + * + * @return the signed file File-object + * @throws IOException + * @throws FileNotFoundException + * @throws CertificateException + * @throws PdfAsException + */ + protected File signPDFFile() throws IOException, FileNotFoundException, + CertificateException, PdfAsException { + File inputFile = new File(baseTestData.getPdfFile()); + File outputPdfFile = new File(baseTestData.getOutputFile()); + DataSource dataSource = new ByteArrayDataSource( + StreamUtils.inputStreamToByteArray(new FileInputStream( + inputFile))); + ByteArrayDataSink dataSink = new ByteArrayDataSink(); + pdfAs = null; + + pdfAs = PdfAsFactory.createPdfAs(new File(baseTestData + .getConfigurationFile())); + Configuration configuration = pdfAs.getConfiguration(); + + signParameter = PdfAsFactory.createSignParameter(configuration, + dataSource); + + String id = UUID.randomUUID().toString(); + signParameter.setTransactionId(id); + logger.debug("Transaction: " + id); + + slConnector = null; + + if (baseTestData.getConnectorData() != null) { + if (baseTestData.getConnectorData().getConnectorType() + .equals("bku")) { + slConnector = new PAdESSigner(new BKUSLConnector(configuration)); + } else if (baseTestData.getConnectorData().getConnectorType() + .equals("moa")) { + slConnector = new PAdESSigner(new MOAConnector(configuration)); + } else if (baseTestData.getConnectorData().getConnectorType() + .equals("ks")) { + Map<String, String> params = baseTestData.getConnectorData() + .getConnectorParameters(); + String keystoreFilename = params + .get(BaseSignatureDataProvider.KS_FILE_NAME); + String keystoreAlias = params + .get(BaseSignatureDataProvider.KS_ALIAS); + String keystoreType = params + .get(BaseSignatureDataProvider.KS_TYPE); + String keystoreStorepass = params + .get(BaseSignatureDataProvider.KS_PASS); + String keystoreKeypass = params + .get(BaseSignatureDataProvider.KS_KEY_PASS); + + assertNotNull( + "You need to provide a keystore file if using ks connector", + keystoreFilename); + assertNotNull( + "You need to provide a key alias if using ks connector", + keystoreAlias); + + slConnector = new PAdESSignerKeystore(keystoreFilename, + keystoreAlias, keystoreStorepass, keystoreKeypass, + keystoreType); + } + } + if (slConnector == null) { + slConnector = new PAdESSigner(new BKUSLConnector(configuration)); + } + + signParameter.setOutput(dataSink); + signParameter.setPlainSigner(slConnector); + signParameter.setDataSource(dataSource); + // this is not needed for PDF-A test + if (positionString != null) + signParameter.setSignaturePosition(positionString); + signParameter.setSignatureProfileId(baseTestData.getProfilID()); + logger.debug("Starting signature for " + baseTestData.getPdfFile()); + logger.debug("Selected signature Profile " + baseTestData.getProfilID()); + /* SignResult result = */pdfAs.sign(signParameter); + FileOutputStream fos = null; + try { + fos = new FileOutputStream(outputPdfFile, false); + fos.write(dataSink.getData()); + fos.close(); + } catch (IOException e) { + logger.debug("IO exception occured while writing PDF output file", + e); + throw e; + } finally { + IOUtils.closeQuietly(fos); + } + + return outputPdfFile; + } +} diff --git a/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/SignatureTestWatcher.java b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/SignatureTestWatcher.java new file mode 100644 index 00000000..104dab7c --- /dev/null +++ b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/SignatureTestWatcher.java @@ -0,0 +1,121 @@ +package at.gv.egiz.param_tests; + +import org.junit.AssumptionViolatedException; +import org.junit.rules.TestWatcher; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +import at.gv.egiz.param_tests.serialization.SerializiationManager; + +/** + * A test watcher, which provides some basic information about executed tests, + * i.e. verdicts and console output. Basically a test watcher is some class, + * which gets notified every time a test started or stopped. + * + * @author mtappler + * + */ +public class SignatureTestWatcher extends TestWatcher { + @Override + public Statement apply(Statement base, Description description) { + return super.apply(base, description); + } + + /** + * This method tells the serialization manager that a test succeeded + * + * @param description + * the description of the test, which contains the display name, + * which in turn contains the directory of the test, which + * uniquely identifies the test + */ + @Override + protected void succeeded(Description description) { + super.succeeded(description); + SerializiationManager.getInstance().setSucceeded( + extractDirectoryName(description.getDisplayName())); + + } + + /** + * Helper method which extracts the directory name of a test given its + * display name. By convention, the display name shall include directory + * names enclosed in angle brackets. + * + * @param displayName + * the name which is displayed for a test + * @return the directory of the test + */ + private String extractDirectoryName(String displayName) { + return displayName.substring(displayName.indexOf('[') + 1, + displayName.lastIndexOf(']')); + + //return displayName.substring(displayName.indexOf('<') + 1, + // displayName.lastIndexOf('>')); + } + + /** + * This method tells the serialization manager that a test failed + * + * @param description + * the description of the test, which contains the display name, + * which in turn contains the directory of the test, which + * uniquely identifies the test + */ + @Override + protected void failed(Throwable e, Description description) { + super.failed(e, description); + SerializiationManager.getInstance().setFailed( + extractDirectoryName(description.getDisplayName()), e); + + } + + /** + * This method tells the serialization manager that a test was skipped, + * because of a failed assumption. + * + * @param description + * the description of the test, which contains the display name, + * which in turn contains the directory of the test, which + * uniquely identifies the test + */ + protected void skipped(AssumptionViolatedException e, + Description description) { + SerializiationManager.getInstance().setInconclusive( + extractDirectoryName(description.getDisplayName()), e); + } + + /** + * This method resets the logging of stdout and stderr, to be able to + * retrieve it after the test has finished. + * + * @param description + * description of a test which was just started + */ + @Override + protected void starting(Description description) { + super.starting(description); + ParameterizedSignatureTestSuite.getStdoutLog().reset(); + ParameterizedSignatureTestSuite.getStderrLog().reset(); + } + + /** + * This method sets stdout and stderr, which was logged during the execution + * of a test. + * + * @param description + * the description of the test, which contains the display name, + * which in turn contains the directory of the test, which + * uniquely identifies the test + */ + @Override + protected void finished(Description description) { + super.finished(description); + SerializiationManager.getInstance().setStdOut( + extractDirectoryName(description.getDisplayName()), + ParameterizedSignatureTestSuite.getStdoutLog().toString()); + SerializiationManager.getInstance().setStdErr( + extractDirectoryName(description.getDisplayName()), + ParameterizedSignatureTestSuite.getStderrLog().toString()); + } +}
\ No newline at end of file diff --git a/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/provider/BaseSignatureDataProvider.java b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/provider/BaseSignatureDataProvider.java new file mode 100644 index 00000000..1ce78e14 --- /dev/null +++ b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/provider/BaseSignatureDataProvider.java @@ -0,0 +1,472 @@ +package at.gv.egiz.param_tests.provider; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.filefilter.WildcardFileFilter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import at.gv.egiz.pdfas.lib.api.PdfAsFactory; + +/** + * Abstract base class for test data providers. The subclasses of it shall read + * configuration properties and provide test parameters for parameterized unit + * tests. This offers some functionality, which is needed by all tests, e.g. it + * reads basic configuration data, which is needed for signing a PDF file, which + * is done by all tests. + * + * @author mtappler + * + */ +public abstract class BaseSignatureDataProvider { + + /** + * config-property key for specifying the type of the test + */ + public static final String TEST_TYPE = "test.type"; + + /** + * config-property key for specifying the name of the test, if no name is + * specified, the test directory will be used as name + */ + public static final String TEST_NAME = "test.name"; + + /** + * config-property for specifying signature profile + */ + public static final String PROFILE_ID = "profile.id"; + + /** + * config-property key for specifying the name of the output file + */ + public static final String OUTPUT_FILE = "output.file"; + /** + * config-property key for specifying the type of connector, which is used + * for signing + */ + public static final String CONNECTOR = "connector"; + /** + * config-property key for PDF-AS configuration file location + */ + public static final String CONFIG_FILE = "config.file"; + /** + * config-property key for PDF input file name, the file which is signed + */ + public static final String INPUT_FILE = "input.file"; + + public static final String PARENT_CFG = "parent"; + + /** + * default location for PDF-AS config file + */ + public static final String STANDARD_CONFIG_LOCATION = System + .getProperty("user.home") + "/.pdfas/"; + + /** + * config-property key for the filename of the keystore, required if ks + * (keystore) is used as connector + */ + public static final String KS_FILE_NAME = "ks.filename"; + + /** + * config-property key for the key alias, required if ks (keystore) is used + * as connector + */ + public static final String KS_ALIAS = "ks.alias"; + + /** + * config-property key for the keystore type, optional value if ks + * (keystore) is used as connector + */ + public static final String KS_TYPE = "ks.type"; + + /** + * config-property key for the password of the keystore, required if ks + * (keystore) is used as connector + */ + public static final String KS_PASS = "ks.pass"; + + /** + * config-property key for the password of the key, required if ks + * (keystore) is used as connector + */ + public static final String KS_KEY_PASS = "ks.keypass"; + + /** + * logger for this class + */ + private static final Logger logger = LoggerFactory + .getLogger(BaseSignatureDataProvider.class); + + /** + * Interface for test classes: This method should be invoked to get the data + * for parameterized unit tests. It calls two template method style (design + * pattern) methods to perform different actions for different test types. + * Generally it loops over all directories in the test directory and filters + * using a wildcard-filter and then calls abstract methods, one to decide if + * the subclass is a provider the given test type + * <code>isSupportedTestType</code> and one to extract configuration data + * <code>extractDataFromConfig</code>. + * + * @return parameters for the parameterized unit tests + */ + public List<Object[]> gatherData() { + String testDir = System.getProperty("test.dir"); + logger.info("Data from: " + testDir); + String testFilter = System.getProperty("test.filter"); + List<Object[]> result = new ArrayList<Object[]>(); + File testDirFile = new File(testDir); + File rootConfigFile = new File(testDirFile, "config.properties"); + Properties rootProp = new Properties(); + InputStream rootIn = null; + try { + rootIn = new FileInputStream(rootConfigFile); + rootProp.load(rootIn); + } catch (IOException e) { + // if we can't get root properties, we just take empty root + // properties + rootProp = new Properties(); + } finally { + IOUtils.closeQuietly(rootIn); + } + + File[] childFiles = null; + if (testFilter == null) { + childFiles = testDirFile.listFiles(); + } else { + String[] wildcards = testFilter.split(";"); + childFiles = testDirFile + .listFiles((FilenameFilter) new WildcardFileFilter( + wildcards)); + } + int idx = 0; + for (File child : childFiles) { + if (child.isDirectory() && directoryContainsConfig(child)) { + File configFile = new File(child, "config.properties"); + Properties prop = new Properties(); + InputStream in = null; + try { + in = new FileInputStream(configFile); + prop.load(in); + + String parent = prop.getProperty(PARENT_CFG); + if(parent != null) { + File parentFile = new File(child, parent); + if(parentFile.exists()) { + prop.clear(); + prop.load(new FileInputStream(parentFile)); + prop.load(new FileInputStream(configFile)); + } + } + + if (isSupportedTestType(prop)) { + Object[] data = extractDataFromConfig(configFile, + rootProp, prop); + result.add(data); + String testName = idx + "-" + data[1].toString(); + ((BaseSignatureTestData)data[2]).setUniqueUnitTestName(testName); + idx++; + } + } catch (IOException e) { + logger.warn( + "Could not run test with config:" + + configFile.getAbsolutePath(), e); + } finally { + if (in != null) + IOUtils.closeQuietly(in); + } + } + } + return result; + } + + /** + * Method to extract test-type specific data/parameters from a config file. + * + * @param configFile + * the File object corresponding to the config file + * @param rootProps + * the properties read from the root config file + * @param configProps + * the properties read from the config file + * @return parameters for one parameterized unit test + */ + protected abstract Object[] extractDataFromConfig(File configFile, + Properties rootProps, Properties configProps); + + /** + * Utility method to extract basic configuration data, which is needed by + * all parameterized unit tests, i.e. test directory, test name and one + * instance of <code>BaseSignatureTestData</code>. + * + * @param configFile + * the File object corresponding to the config file + * @param rootProps + * the properties read from the root config file + * @param configProps + * the properties read from the config file + * @return basic parameters for one parameterized unit test + */ + protected Object[] extractStandardDataFromConfig(File configFile, + Properties rootProps, Properties configProps) { + String testDirectory = null; + try { + testDirectory = configFile.getParentFile().getCanonicalPath(); + } catch (IOException e) { + // getCanonicalPath() might again throw an exception + testDirectory = configFile.getAbsolutePath().replace( + "/config.properties", ""); + } + String testName = getTestName(configFile, configProps); + String configurationFile = provideConfigFile(rootProps, configProps); + String profilID = getProperty(rootProps, configProps, PROFILE_ID); + String pdfFile = getPDFFileName(configFile, configProps); + String outputFile = getOutputFileName(pdfFile, configFile, configProps); + ConnectorData connectorData = provideConnectorData(rootProps, + configProps); + return new Object[] { + testDirectory, + testName, + new BaseSignatureTestData(testDirectory, testName, + configurationFile, profilID, pdfFile, outputFile, + connectorData) }; + } + + /** + * Test if the concrete provider implementation supports a test given by + * properties. + * + * @param prop + * the properties file of this test + * @return true if the test is suppported, false otherwise + */ + protected abstract boolean isSupportedTestType(Properties prop); + + /** + * Helper method to check if a directory contains a file called + * "config.properties". + * + * @param file + * the File object corresponding to the directory + * @return true if the directory contains a config file + */ + private boolean directoryContainsConfig(File file) { + return Arrays.asList(file.list()).contains("config.properties"); + } + + /** + * Retrieves the test name, which should either be specified in the config + * file for the test case (not in the root properties) or be equal to the + * directory name of the test. + * + * @param configFile + * file object for the config file + * @param configProps + * the properties defined in the config file + * @return the test name + */ + protected String getTestName(File configFile, Properties configProps) { + if (configProps.containsKey(TEST_NAME)) { + return configProps.getProperty(TEST_NAME); + } else { + int lastSlash = configFile.getAbsolutePath().lastIndexOf('/'); + int secondToLastSlash = configFile.getAbsolutePath().lastIndexOf( + '/', lastSlash - 1); + return configFile.getAbsolutePath().substring( + secondToLastSlash + 1, lastSlash); + } + } + + /** + * This method retrieves the configured location of the PDF-AS configuration + * file. It may return the standard configuration location if no location is + * specified. + * + * @param rootProps + * root properties, i.e. properties specified in the test + * directory + * @param configProps + * properties specific for one test case + * @return location of the property file + */ + protected String provideConfigFile(Properties rootProps, + Properties configProps) { + String configurationFile = getProperty(rootProps, configProps, + CONFIG_FILE); + if (configurationFile == null) { + configurationFile = STANDARD_CONFIG_LOCATION; + deployConfigIfNotexisting(); + } + return configurationFile; + } + + /** + * Deploys a PDF-AS config in the standard location if it does not already + * exist, same as in pdf-as-cli (Main.java). + */ + private static void deployConfigIfNotexisting() { + File configurationLocation = new File(STANDARD_CONFIG_LOCATION); + try { + if (!configurationLocation.exists()) { + PdfAsFactory.deployDefaultConfiguration(configurationLocation); + } + } catch (Exception e) { + logger.warn("Failed to deploy default confiuration to " + + configurationLocation.getAbsolutePath(), e); + } + } + + /** + * Retrieves the file name of the PDF input file for a test, i.e. the file + * which should be signed. If the configuration properties do not specify an + * input file, the first file found in the test case directory is used. + * + * @param configFile + * the config file for the test + * @param configProps + * the config properties for the test + * @return the input file name + */ + protected String getPDFFileName(File configFile, Properties configProps) { + String dirString = configFile.getParent() + "/"; + if (configProps.containsKey(INPUT_FILE)) + return dirString + configProps.getProperty(INPUT_FILE); + else { + // just return the first pdf file found in the TC directory + // or null for which we need to check in the TC + String[] pdfsInDir = configFile.getParentFile().list( + new FilenameFilter() { + public boolean accept(File dir, String name) { + return name.endsWith(".pdf"); + } + }); + if (pdfsInDir.length == 0) + return null; + else + return dirString + pdfsInDir[0]; + + } + } + + /** + * Retrieves the file name of the PDF output file for a test, i.e. the file + * which is signed. If the configuration properties do not specify an output + * file, "_signed" is appended to the name of the input file and used as + * name. + * + * @param configFile + * the config file for the test + * @param configProps + * the config properties for the test + * @return the output file name + */ + protected String getOutputFileName(String pdfFile, File configFile, + Properties configProps) { + String outputFile = configProps.getProperty(OUTPUT_FILE); + if (outputFile == null) { + if (pdfFile.endsWith(".pdf")) { + outputFile = pdfFile.subSequence(0, + pdfFile.length() - ".pdf".length()) + + "_signed.pdf"; + } else { + outputFile = pdfFile + "_signed.pdf"; + } + outputFile = outputFile.substring(outputFile.lastIndexOf('/') + 1, + outputFile.length()); + } + new File(configFile.getParent() + "/out/").mkdir(); + return configFile.getParent() + "/out/" + outputFile; + + } + + /** + * This method reads configuration properties and returns connector data, + * i.e. data specifying which connector should be used together with which + * parameters. + * + * @param rootProps + * root properties, i.e. properties specified in the test + * directory + * @param configProps + * properties specific for one test case + * @return connector data + */ + protected ConnectorData provideConnectorData(Properties rootProps, + Properties configProps) { + if (getProperty(rootProps, configProps, CONNECTOR) != null) { + String connectorType = getProperty(rootProps, configProps, + CONNECTOR); + if (connectorType.equalsIgnoreCase("bku")) { + return new ConnectorData("bku", + Collections.<String, String> emptyMap()); + } else if (connectorType.equalsIgnoreCase("moa")) { + return new ConnectorData("moa", + Collections.<String, String> emptyMap()); + } else if (connectorType.equalsIgnoreCase("ks")) { + Map<String, String> connectorParamaters = new HashMap<String, String>(); + connectorParamaters.put(KS_FILE_NAME, + getProperty(rootProps, configProps, KS_FILE_NAME)); + connectorParamaters.put(KS_ALIAS, + getProperty(rootProps, configProps, KS_ALIAS)); + if (getProperty(rootProps, configProps, KS_TYPE) != null) { + connectorParamaters.put(KS_TYPE, + getProperty(rootProps, configProps, KS_TYPE)); + } else { + connectorParamaters.put(KS_TYPE, "PKCS12"); + logger.debug("Defaulting to PKCS12 keystore type."); + } + if (getProperty(rootProps, configProps, KS_PASS) != null) { + connectorParamaters.put(KS_PASS, + getProperty(rootProps, configProps, KS_PASS)); + } else { + connectorParamaters.put(KS_PASS, ""); + } + if (getProperty(rootProps, configProps, KS_KEY_PASS) != null) { + connectorParamaters.put(KS_KEY_PASS, + getProperty(rootProps, configProps, KS_KEY_PASS)); + } else { + connectorParamaters.put(KS_KEY_PASS, ""); + } + return new ConnectorData("ks", connectorParamaters); + } + } + return null; + } + + /** + * Helper method which looks in the Properties-maps for the value of a given + * key. If the value is found in <code>configProps</code> then this value is + * used, otherwise the value found in <code>rootProps</code> is used. This + * way, test properties can override root properties. + * + * @param rootProps + * root properties, i.e. properties specified in the test + * directory + * @param configProps + * properties specific for one test case + * @param key + * a config-property key + * @return the value for the key + */ + protected String getProperty(Properties rootProps, Properties configProps, + String key) { + if (configProps.containsKey(key)) + return configProps.getProperty(key); + else if (rootProps.containsKey(key)) + return rootProps.getProperty(key); + else + return null; + } +} diff --git a/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/provider/BaseSignatureTestData.java b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/provider/BaseSignatureTestData.java new file mode 100644 index 00000000..c8074b4f --- /dev/null +++ b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/provider/BaseSignatureTestData.java @@ -0,0 +1,131 @@ +package at.gv.egiz.param_tests.provider; + +/** + * Class holding data, which is used by all parameterized signature tests, e.g. + * basic data needed for signing. It does not contain any logic at all + * + * @author mtappler + * + */ +public class BaseSignatureTestData { + + /** + * location of the PDF-AS configuration file + */ + private String configurationFile; + /** + * signature profile ID + */ + private String profilID; + /** + * location of the input PDF file which should be signed + */ + private String pdfFile; + /** + * location of the output PDF file which is the signed version of the input + * file + */ + private String outputFile; + /** + * connector data, which is used to determine the signer + * (IPlainSigner-interface) + */ + private ConnectorData connectorData; + /** + * the name of the test + */ + private String testName; + /** + * directory name of the test + */ + private String testDirectory; + + private String uniqueUnitTestName; + + /** + * Constructor initializing all attributes. + * + * @param testDirectory + * @param testName + * @param configurationFile + * @param profilID + * @param pdfFile + * @param outputFile + * @param connectorData + */ + public BaseSignatureTestData(String testDirectory, String testName, + String configurationFile, String profilID, String pdfFile, + String outputFile, ConnectorData connectorData) { + this.testDirectory = testDirectory; + this.testName = testName; + this.configurationFile = configurationFile; + this.profilID = profilID; + this.pdfFile = pdfFile; + this.outputFile = outputFile; + this.connectorData = connectorData; + } + + public String getConfigurationFile() { + return configurationFile; + } + + public void setConfigurationFile(String configurationFile) { + this.configurationFile = configurationFile; + } + + public String getProfilID() { + return profilID; + } + + public void setProfilID(String profilID) { + this.profilID = profilID; + } + + public String getPdfFile() { + return pdfFile; + } + + public void setPdfFile(String pdfFile) { + this.pdfFile = pdfFile; + } + + public String getOutputFile() { + return outputFile; + } + + public void setOutputFile(String outputFile) { + this.outputFile = outputFile; + } + + public ConnectorData getConnectorData() { + return connectorData; + } + + public void setConnectorData(ConnectorData connectorData) { + this.connectorData = connectorData; + } + + public String getTestName() { + return testName; + } + + public void setTestName(String testName) { + this.testName = testName; + } + + public String getTestDirectory() { + return testDirectory; + } + + public void setTestDirectory(String testDirectory) { + this.testDirectory = testDirectory; + } + + public String getUniqueUnitTestName() { + return uniqueUnitTestName; + } + + public void setUniqueUnitTestName(String uniqueUnitTestName) { + this.uniqueUnitTestName = uniqueUnitTestName; + } +} diff --git a/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/provider/ConnectorData.java b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/provider/ConnectorData.java new file mode 100644 index 00000000..8216358c --- /dev/null +++ b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/provider/ConnectorData.java @@ -0,0 +1,54 @@ +package at.gv.egiz.param_tests.provider; + +import java.util.Map; + +/** + * Connector data class which specifies which + * connector/IPlainSigner-implementation to use. + * + * @author mtappler + * + */ +public class ConnectorData { + /** + * the type of the connector, named the same as in the CLI + */ + private String connectorType; + /** + * additional parameters like key alias if a keystore is used + */ + private Map<String, String> connectorParameters; + + /** + * Constructor initializing both attributes. + * + * @param connectorType + * type of the connector + * @param connectorParameters + * additional parameters + */ + public ConnectorData(String connectorType, + Map<String, String> connectorParameters) { + super(); + this.connectorType = connectorType; + this.connectorParameters = connectorParameters; + } + + /** + * getter + * + * @return type of the connector to use + */ + public String getConnectorType() { + return connectorType; + } + + /** + * getter + * + * @return additional parameters for the connector + */ + public Map<String, String> getConnectorParameters() { + return connectorParameters; + } +} diff --git a/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/provider/PDFAProvider.java b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/provider/PDFAProvider.java new file mode 100644 index 00000000..b8903268 --- /dev/null +++ b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/provider/PDFAProvider.java @@ -0,0 +1,34 @@ +package at.gv.egiz.param_tests.provider; + +import java.io.File; +import java.util.Properties; + +/** + * Signature data provider for PDF-A conformance tests + * + * @author mtappler + * + */ +public class PDFAProvider extends BaseSignatureDataProvider { + + /** + * This method extracts signature test parameters for performing + * PDF-A conformance tests. It only provides standard parameters, because + * it does not need any other parameters. + */ + @Override + protected Object[] extractDataFromConfig(File configFile, + Properties rootProps, Properties configProps) { + return extractStandardDataFromConfig(configFile, rootProps, configProps); + } + + /** + * This method checks if this provider supports a given test configuration. + * In order to be supported the test type must be "pdfa". + */ + @Override + protected boolean isSupportedTestType(Properties configProps) { + return configProps.containsKey(TEST_TYPE) + && configProps.get(TEST_TYPE).equals("pdfa"); + } +} diff --git a/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/provider/SignaturePositionProvider.java b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/provider/SignaturePositionProvider.java new file mode 100644 index 00000000..34727876 --- /dev/null +++ b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/provider/SignaturePositionProvider.java @@ -0,0 +1,110 @@ +package at.gv.egiz.param_tests.provider; + +import java.awt.Rectangle; +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +public class SignaturePositionProvider extends BaseSignatureDataProvider { + + /** + * config-property value for "position.mode", if this mode is chosen, only + * reference image is captured and no tests are performed + */ + public static final String CAPTURE_REFERENCE = "capture_reference"; + /** + * config-property key for the positioning-string, which has the same syntax + * as in the CLI + */ + public static final String POSITIONING_STRING = "position.positioning_string"; + /** + * config-property key for the number of the page on which the signature is + * expected to appear + */ + public static final String SIG_PAGE_NUMBER = "position.page_number"; + /** + * config-property key for the name of the reference image + */ + public static final String REFERENCE_IMAGE = "position.reference_image"; + /** + * config-property key for the areas which are ignored for image comparison + */ + public static final String IGNORED_AREAS = "position.ignored_areas"; + /** + * config-property key for mode to use for the test, it can either be + * "capture_reference" or anything else + */ + public static final String MODE = "position.mode"; + + /** + * This method extracts in addition to the standard parameters also + * signature position test parameters from the config properties. The keys + * for these properties are defined as string constants. + */ + @Override + protected Object[] extractDataFromConfig(File configFile, + Properties rootProps, Properties configProps) { + Object[] standardData = extractStandardDataFromConfig(configFile, + rootProps, configProps); + Object[] data = new Object[standardData.length + 5]; + int i; + for (i = 0; i < standardData.length; i++) { + data[i] = standardData[i]; + } + data[i++] = getProperty(rootProps, configProps, POSITIONING_STRING); + data[i++] = Integer.parseInt(getProperty(rootProps, configProps, + SIG_PAGE_NUMBER)); + String ignoredAreaStringProp = getProperty(rootProps, configProps, + IGNORED_AREAS); + List<Rectangle> ignoredAreas = new ArrayList<Rectangle>(); + if (ignoredAreaStringProp != null) { + String[] ignoredAreaStringSplit = ignoredAreaStringProp.split(";"); + for (String ignoredAreaString : ignoredAreaStringSplit) { + Rectangle ignoredArea = parseIgnoredArea(ignoredAreaString); + if (ignoredArea != null) { + ignoredAreas.add(ignoredArea); + } + } + } + data[i++] = ignoredAreas; + data[i++] = getProperty(rootProps, configProps, REFERENCE_IMAGE); + data[i++] = CAPTURE_REFERENCE.equals(getProperty(rootProps, + configProps, MODE)); + return data; + } + + /** + * This parses one ignored area definition and returns a Rectangle-object + * representing it. The definitions have the exact format + * "<x>,<y>,<width>,<height>", with x and y specifying the coordinates of + * the upper left corner of the area and width and height specifying the + * size in pixels (width and height extends to the right and the bottom of + * the image). + * + * @param ignoredAreaString + * an ignored area definition + * @return a rectangle representing the area + */ + private Rectangle parseIgnoredArea(String ignoredAreaString) { + String[] ignoredAreaStringSplit = ignoredAreaString.split(","); + if (ignoredAreaStringSplit.length != 4) + return null; + int x = Integer.parseInt(ignoredAreaStringSplit[0]); + int y = Integer.parseInt(ignoredAreaStringSplit[1]); + int width = Integer.parseInt(ignoredAreaStringSplit[2]); + int height = Integer.parseInt(ignoredAreaStringSplit[3]); + return new Rectangle(x, y, width, height); + } + + /** + * This method checks if this provider supports a given test configuration. + * In order to be supported the test type must be "position". + */ + @Override + protected boolean isSupportedTestType(Properties configProps) { + return configProps.containsKey(TEST_TYPE) + && configProps.get(TEST_TYPE).equals("position"); + + } +} diff --git a/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/serialization/SerializiationManager.java b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/serialization/SerializiationManager.java new file mode 100644 index 00000000..294084eb --- /dev/null +++ b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/serialization/SerializiationManager.java @@ -0,0 +1,254 @@ +package at.gv.egiz.param_tests.serialization; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.AssumptionViolatedException; + +import at.gv.egiz.param_tests.provider.BaseSignatureTestData; +import at.gv.egiz.param_tests.testinfo.TestInfo; +import at.gv.egiz.param_tests.testinfo.TestVerdict; + +/** + * Singleton managing the serialization of test data. Generally there is one + * serializer prototype per test type and one serializer clone per test which + * was run. Each serializer clone holds the data of exactly one test and is able + * to serizalize in some format. Additionally to that there is also a + * TestSummaryWriter-object which writes a summary of all test results. + * + * @author mtappler + * + */ +public class SerializiationManager { + + /** + * the singleton instance + */ + private static SerializiationManager instance = null; + + /** + * The singleton accessor method. It is not used synchronized as it is + * expected that the parameterized unit tests won't be run in parallel. + * + * @return the singleton instance + */ + public static SerializiationManager getInstance() { + // implement locking if needed + if (instance == null) { + instance = new SerializiationManager(); + } + return instance; + } + + /** + * a map of serializer prototypes, the key is the canonical name of the + * TestInfo-subclass which the prototype can serializer and the value is the + * prototype itself + */ + private Map<String, TestInfoSerializer<? extends TestInfo>> serializerPrototypes = new HashMap<String, TestInfoSerializer<? extends TestInfo>>(); + + /** + * a list of serializer clones, with one element for each test + */ + private List<TestInfoSerializer<? extends TestInfo>> serializers = new ArrayList<TestInfoSerializer<? extends TestInfo>>(); + /** + * the test summary, which writes the summary of test results all tests + */ + private TestSummaryWriter testSummaryWriter; + + /** + * protected constructor + */ + protected SerializiationManager() { + } + + /** + * This method registers a serializer for a TestInfo-subclass. + * + * @param serializer + * a serializer prototype + * @param testInfoClass + * the class object corresponding to the TestInfo-subclass + */ + public <T extends TestInfo> void registerSerializer( + TestInfoSerializer<T> serializer, Class<T> testInfoClass) { + serializerPrototypes.put(testInfoClass.getCanonicalName(), serializer); + } + + /** + * This methods creates an instance of a TestInfo-subclass and returns. + * Actually a serializer is clone and the clone creates the instance and + * holds it to serialize it later. + * + * @param testInfoClass + * the class of the requested test information object + * @param baseData + * basic test data which is set for all test information object + * @return a test information object + */ + public <T extends TestInfo> T createTestInfo(Class<T> testInfoClass, + BaseSignatureTestData baseData) { + @SuppressWarnings("unchecked") + // unfortunately this cast is necessary + TestInfoSerializer<T> serializer = (TestInfoSerializer<T>) serializerPrototypes + .get(testInfoClass.getCanonicalName()); + // if there is no test info, the test will fail with a null pointer + // exception + // if it tries to access the test info + if (serializer == null) + return null; + TestInfoSerializer<T> serializerClone = serializer.cloneSerializer(); + serializers.add(serializerClone); + T tInfo = serializerClone.createTestInfo(); + tInfo.setBaseTestData(baseData); + + return tInfo; + } + + /** + * Sets a test given its directory name as succeeded. The directory name is + * used, because it uniquely defines the test, as there is only one test per + * directory and it is encoded in display name of the test. + * + * @param directoryName + * the name of the directory of the test + */ + public void setSucceeded(String directoryName) { + TestInfo testInfo = getTestInfoForDirectory(directoryName); + if (testInfo != null) { + testInfo.setVerdict(TestVerdict.SUCCEEDED); + } + } + + /** + * Retrieves test information for a test. + * + * @param directoryName + * the name of the directory of the test + * @return basic test information + */ + private TestInfo getTestInfoForDirectory(String directoryName) { + for (TestInfoSerializer<?> s : serializers) { + if (directoryName.startsWith(s.getBaseTestInfo().getBaseTestData() + .getUniqueUnitTestName())) + return s.getBaseTestInfo(); + } + return null; + } + + /** + * Sets a test given its directory name as failed and sets a + * <code>Throwable</code>-object as fail cause. The directory name is used, + * because it uniquely defines the test, as there is only one test per + * directory and it is encoded in display name of the test. + * + * @param directoryName + * the name of the directory of the test + * @param e + * the cause of the fail + */ + public void setFailed(String directoryName, Throwable e) { + TestInfo testInfo = getTestInfoForDirectory(directoryName); + if (testInfo != null) { + testInfo.setVerdict(TestVerdict.FAILED); + testInfo.setFailCause(e); + } + } + + /** + * Serializes all test results including a summary. After a call to this + * function there will be an index file with the summary in the root test + * directory and a test results file in all directories containing tests. + */ + public void serializeAll() { + testSummaryWriter.init(); + testSummaryWriter.writeHeader(); + Collections.sort(serializers, + new Comparator<TestInfoSerializer<? extends TestInfo>>() { + public int compare( + TestInfoSerializer<? extends TestInfo> o1, + TestInfoSerializer<? extends TestInfo> o2) { + String firstDir = o1.getBaseTestInfo() + .getBaseTestData().getTestDirectory(); + String secondDir = o2.getBaseTestInfo() + .getBaseTestData().getTestDirectory(); + return firstDir.compareTo(secondDir); + } + }); + + for (TestInfoSerializer<?> s : serializers) { + testSummaryWriter.writeSummaryOfTest(s.getBaseTestInfo(), + s.getTestType()); + s.serialize(); + } + testSummaryWriter.writeFooter(); + testSummaryWriter.close(); + } + + /** + * Sets a test summary writer. This method must be call before + * serializeAll() is called, because this method relies on the presence of a + * test summary writer. + * + * @param testSummaryWriter + * a concrete implementation of <code>TestSummaryWriter</code> + */ + public void setTestSummaryWriter(TestSummaryWriter testSummaryWriter) { + this.testSummaryWriter = testSummaryWriter; + } + + /** + * Sets the content which was written to the standard output during a test + * for a test. + * + * @param directoryName + * the directory name of the test + * @param stdOutFromTest + * the standard output content + */ + public void setStdOut(String directoryName, String stdOutFromTest) { + TestInfo testInfo = getTestInfoForDirectory(directoryName); + testInfo.setStdOut(stdOutFromTest); + } + + /** + * Sets the content which was written to the standard error during a test + * for a test. + * + * @param directoryName + * the directory name of the test + * @param stdErrFromTest + * the standard error content + */ + public void setStdErr(String directoryName, String stdErrFromTest) { + TestInfo testInfo = getTestInfoForDirectory(directoryName); + testInfo.setStdErr(stdErrFromTest); + } + + /** + * Sets a test given its directory name as inconclusive and sets a + * <code>AssumptionViolatedException</code>-object as cause for the verdict. + * The directory name is used, because it uniquely defines the test, as + * there is only one test per directory and it is encoded in display name of + * the test. A test may be inconclusive if an assumption is violated,e.g. if + * PDF-A conformance should be checked after signing and the input file does + * not conform to the PDF-A standard then the test cannot perform any + * meaningful checks. + * + * @param directoryName + * the name of the directory of the test + * @param e + * the cause of the fail + */ + public void setInconclusive(String directoryName, + AssumptionViolatedException e) { + TestInfo testInfo = getTestInfoForDirectory(directoryName); + testInfo.setVerdict(TestVerdict.INCONCLUSIVE); + testInfo.setFailCause(e); + } + +} diff --git a/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/serialization/TestInfoSerializer.java b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/serialization/TestInfoSerializer.java new file mode 100644 index 00000000..a3014b86 --- /dev/null +++ b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/serialization/TestInfoSerializer.java @@ -0,0 +1,170 @@ +package at.gv.egiz.param_tests.serialization; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.UnsupportedEncodingException; + +import org.apache.commons.io.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import at.gv.egiz.param_tests.testinfo.TestInfo; + +/** + * Base for all test information serializer. It uses template method pattern, + * fixing an algorithm for serialization, but delegating the actual steps to the + * subclasses. A two-step hierarchy shall be formed, with this class at the + * upper-most level, technology-specific (like HTML) serializer at the next and + * technology- and test-type-specific serializer at the lowest level. + * + * @author mtappler + * + * @param <T> + * the type of TestInfo which instance of this class, respectively + * subclasses of it can serialize + */ +public abstract class TestInfoSerializer<T extends TestInfo> { + + /** + * logger for this class + */ + private static final Logger logger = LoggerFactory + .getLogger(TestInfoSerializer.class); + + /** + * basic test information + */ + protected TestInfo baseTestInfo; + + /** + * Method for creating an of a subclass of <code>TestInfo</code>. The test + * information created using this method shall be retrieved, when + * <code>getBaseTestInfo()</code> is called. + * + * @return instance of a <code>TestInfo</code>-subclass + */ + public abstract T createTestInfo(); + + /** + * Clone method for serializer, it shall create a shallow copy of + * <code>this</code> and return it. It does not use + * <code>Object.clone()</code> for type safety, because this method returns + * an <code>Object</code>-instance. + * + * @return a clone of <code>this</code> + */ + public abstract TestInfoSerializer<T> cloneSerializer(); + + /** + * getter + * + * @return basic test information + */ + public TestInfo getBaseTestInfo() { + return baseTestInfo; + } + + /** + * Main serialization method. This method creates a test result file in the + * test directory and then calls the methods to perform the serialization + * steps. The following parts shall be written: + * <ol> + * <li>header</li> + * <li>test specific parameter</li> + * <li>test result</li> + * <li>test specific data</li> + * <li>footer</li> + * </ol> + */ + public void serialize() { + File outputFile = new File(baseTestInfo.getBaseTestData() + .getTestDirectory() + "/test_result." + fileEnding()); + FileOutputStream os = null; + try { + os = new FileOutputStream(outputFile); + PrintWriter pw = new PrintWriter(new OutputStreamWriter(os, "UTF8")); + writeHeader(pw); + writeTestSpecificParameters(pw); + writeTestResult(pw); + writeTestData(pw); + writeFooter(pw); + pw.flush(); + } catch (FileNotFoundException e) { + logger.warn("File not found for test info serialization.", e); + } catch (UnsupportedEncodingException e) { + logger.warn("Use unsupported encoding for serialization.", e); + } finally { + if (os != null) + IOUtils.closeQuietly(os); + } + } + + /** + * This writes the test result in some format like HTML to the provided + * <code>PrintWriter</code>-object. + * + * @param pw + * the <code>PrintWriter</code>-object whill shall be used for + * serialization + */ + protected abstract void writeTestResult(PrintWriter pw); + + /** + * This writes test specific parameters in some format like HTML to the + * provided <code>PrintWriter</code>-object. + * + * @param pw + * the <code>PrintWriter</code>-object whill shall be used for + * serialization + */ + protected abstract void writeTestSpecificParameters(PrintWriter pw); + + /** + * This writes the file header in some format like HTML to the provided + * <code>PrintWriter</code>-object. + * + * @param pw + * the <code>PrintWriter</code>-object whill shall be used for + * serialization + */ + protected abstract void writeHeader(PrintWriter pw); + + /** + * This writes test specific data in some format like HTML to the provided + * <code>PrintWriter</code>-object. + * + * @param pw + * the <code>PrintWriter</code>-object whill shall be used for + * serialization + */ + protected abstract void writeTestData(PrintWriter pw); + + /** + * This writes the file footer in some format like HTML to the provided + * <code>PrintWriter</code>-object. + * + * @param pw + * the <code>PrintWriter</code>-object whill shall be used for + * serialization + */ + protected abstract void writeFooter(PrintWriter pw); + + /** + * This method returns the file ending used for the test result file, like + * "html" or "xml". + * + * @return the file ending string + */ + public abstract String fileEnding(); + + /** + * This method returns a description of the concrete test type, which a + * concrete implementation of this class serializes. + * + * @return a test type description + */ + public abstract String getTestType(); +} diff --git a/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/serialization/TestSummaryWriter.java b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/serialization/TestSummaryWriter.java new file mode 100644 index 00000000..ad0dff7d --- /dev/null +++ b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/serialization/TestSummaryWriter.java @@ -0,0 +1,45 @@ +package at.gv.egiz.param_tests.serialization; + +import at.gv.egiz.param_tests.testinfo.TestInfo; + +/** + * interface defining methods for a test summary writer. This shall be + * implemented in a technology-dependent way, like for HTML. + * + * @author mtappler + * + */ +public interface TestSummaryWriter { + + /** + * This method shall write a header to a file. + */ + public void writeHeader(); + + /** + * This method shall write a short summary of a test to a file. + * + * @param tInfo + * test information for the test + * @param testType + * the type of the test + */ + public void writeSummaryOfTest(TestInfo tInfo, String testType); + + /** + * This method shall write a footer to the file. + */ + public void writeFooter(); + + /** + * This method shall initialize the writing process, e.g. by creating and + * opening a file, to which the summary is written. + */ + public void init(); + + /** + * This method shall terminate the writing process, e.g. by closing the + * summary file. + */ + public void close(); +} diff --git a/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/serialization/html/HTMLSerializer.java b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/serialization/html/HTMLSerializer.java new file mode 100644 index 00000000..9bbb0497 --- /dev/null +++ b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/serialization/html/HTMLSerializer.java @@ -0,0 +1,204 @@ +package at.gv.egiz.param_tests.serialization.html; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Map.Entry; + +import at.gv.egiz.param_tests.provider.BaseSignatureTestData; +import at.gv.egiz.param_tests.serialization.TestInfoSerializer; +import at.gv.egiz.param_tests.testinfo.TestInfo; +import at.gv.egiz.param_tests.testinfo.TestVerdict; + +/** + * A subclass implementing some methods, which can be implemented independent of + * concrete test type. It uses the Twitter-bootstrap framework, which must be + * provided for the files to be correctly displayed in a browser. + * + * @author mtappler + * + * @param <T> + * the type of TestInfo which instance of this class, respectively + * subclasses of it can serialize + */ +public abstract class HTMLSerializer<T extends TestInfo> extends + TestInfoSerializer<T> { + + @Override + public String fileEnding() { + return "html"; + } + + @Override + protected void writeHeader(PrintWriter pw) { + BaseSignatureTestData baseData = baseTestInfo.getBaseTestData(); + pw.println("<!doctype html>"); + pw.println("<html>"); + pw.println("<head>"); + pw.println("<title>Test results for " + baseData.getTestName() + + "</title>"); + pw.println("<link rel=\"stylesheet\" type=\"text/css\" href=\"../css/bootstrap.min.css\">"); + pw.println("<link rel=\"stylesheet\" type=\"text/css\" href=\"../css/bootstrap-theme.min.css\">"); + pw.println("<meta charset=\"UTF-8\">"); + pw.println("</head>"); + pw.println("<body>"); + pw.println("<div class=\"container\">"); + pw.println("<div class=\"page-header\">"); + pw.println("<h1>Detailed test results <small>" + baseData.getTestName() + + "</small></h1>"); + pw.println("</div>"); + String basicTestDataPanel = createBasicTestData(); + writePanel(pw, "Basic test data", basicTestDataPanel); + } + + /** + * Helper method which creates three tabs, one for basic test parameter, one + * for the standard output and one for standard error. + * + * @return HTML-string defining the tabs + */ + protected String createBasicTestData() { + BaseSignatureTestData baseData = baseTestInfo.getBaseTestData(); + String basicParameterBody = createBasicParameterRepresentation(baseData); + StringBuilder basicTestDataPanel = new StringBuilder(); + basicTestDataPanel + .append("<ul id=\"tabs\" class=\"nav nav-tabs\" data-tabs=\"tabs\">"); + basicTestDataPanel + .append("<li class=\"active\"><a href=\"#basic-parameters\" " + + "data-toggle=\"tab\">Basic parameters</a></li>"); + basicTestDataPanel + .append("<li><a href=\"#std-out\" data-toggle=\"tab\">Standard output</a></li>"); + basicTestDataPanel + .append("<li><a href=\"#std-err\" data-toggle=\"tab\">Standard error</a></li>"); + basicTestDataPanel.append("</ul>"); + basicTestDataPanel + .append("<div id=\"my-tab-content\" class=\"tab-content\">"); + basicTestDataPanel + .append("<div class=\"tab-pane active\" id=\"basic-parameters\">"); + basicTestDataPanel.append(basicParameterBody); + basicTestDataPanel.append("</div>"); + basicTestDataPanel.append("<div class=\"tab-pane\" id=\"std-out\">"); + basicTestDataPanel.append("<pre class=\"pre-scrollable\">" + + baseTestInfo.getStdOut() + "</pre>"); + basicTestDataPanel.append("</div>"); + basicTestDataPanel.append("<div class=\"tab-pane\" id=\"std-err\">"); + basicTestDataPanel.append("<pre class=\"pre-scrollable\">" + + baseTestInfo.getStdErr() + "</pre>"); + basicTestDataPanel.append("</div>"); + basicTestDataPanel.append("</div>"); + return basicTestDataPanel.toString(); + } + + /** + * This method creates a definition list for basic parameters of a test. + * + * @param baseData + * basic test parameters + * @return HTML-string defining a definition list + */ + private String createBasicParameterRepresentation( + BaseSignatureTestData baseData) { + StringBuilder sb = new StringBuilder(); + sb.append("<dl>"); + sb.append(createDescription("Input file", baseData.getPdfFile())); + sb.append(createDescription("Output file", baseData.getOutputFile())); + sb.append(createDescription("Signature profile", baseData.getProfilID())); + sb.append(createDescription("Connector Type", baseData + .getConnectorData().getConnectorType())); + if (baseData.getConnectorData().getConnectorParameters().size() > 0) { + StringBuilder connectorParameters = new StringBuilder(); + connectorParameters.append("<dl class=\"dl-horizontal\">"); + for (Entry<String, String> e : baseData.getConnectorData() + .getConnectorParameters().entrySet()) + connectorParameters.append(createDescription(e.getKey(), + e.getValue())); + connectorParameters.append("</dl>"); + sb.append(createDescription("Connector Parameters", + connectorParameters.toString())); + } + sb.append(createDescription("Test Type", getTestType())); + sb.append("</dl>"); + return sb.toString(); + } + + /** + * Helper for writing a bootstrap panel with some title and content. + * + * @param pw + * <code>PrintWriter</code>-object to which the panel should be + * written + * @param panelTitle + * title of the panel + * @param panelBody + * panel content + */ + protected void writePanel(PrintWriter pw, String panelTitle, + String panelBody) { + pw.println("<div class=\"panel panel-default\">"); + pw.println(" <div class=\"panel-heading\">"); + pw.println("<h3 class=\"panel-title\">" + panelTitle + "</h3>"); + pw.println("</div>"); + pw.println("<div class=\"panel-body\">"); + pw.println(panelBody); + pw.println("</div>"); + pw.println("</div>"); + } + + @Override + protected void writeTestResult(PrintWriter pw) { + StringBuilder panelBody = new StringBuilder(); + panelBody.append("<h4>" + + HTMLTestSummaryWriter.verdictToLabel(baseTestInfo + .getVerdict()) + "</h4>"); + if (baseTestInfo.getVerdict().equals(TestVerdict.FAILED) + || baseTestInfo.getVerdict().equals(TestVerdict.INCONCLUSIVE)) { + panelBody.append(createExceptionDataString(baseTestInfo + .getFailCause())); + } + writePanel(pw, "Test result", panelBody.toString()); + + } + + /** + * This method creates a HTML-representation for data contained in a + * throwable. + * + * @param t + * <code>Throwable</code>-object which should be displayed + * @return HTML-string for the throwable + */ + protected String createExceptionDataString(Throwable t) { + StringBuilder exceptionData = new StringBuilder(); + exceptionData.append("<dl class=\"dl-horizontal\">"); + exceptionData.append(createDescription("Cause", t.toString())); + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + t.printStackTrace(pw); + exceptionData.append(createDescription("Stacktrace", sw.toString())); + exceptionData.append("</dl>"); + return exceptionData.toString(); + } + + /** + * Helper method for creating an item of a definition list. + * + * @param term + * the term of the item (the key) + * @param definition + * the definition of the item (the value) + * @return an HTML-string for the definition list item + */ + protected String createDescription(String term, String definition) { + return String.format("<dt>%s</dt><dd>%s</dd>%n", term, definition); + } + + @Override + protected void writeFooter(PrintWriter pw) { + pw.println("<a href=\"../index.html\">Back to summary</a>"); + pw.println("<script src=\"https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js\">" + + "</script>"); + pw.println("<script src=\"../js/bootstrap.min.js\"></script>"); + pw.println("</div>"); + pw.println("</body>"); + pw.println("</html>"); + } +} diff --git a/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/serialization/html/HTMLTestSummaryWriter.java b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/serialization/html/HTMLTestSummaryWriter.java new file mode 100644 index 00000000..0734a3ec --- /dev/null +++ b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/serialization/html/HTMLTestSummaryWriter.java @@ -0,0 +1,150 @@ +package at.gv.egiz.param_tests.serialization.html; + +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.UnsupportedEncodingException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import at.gv.egiz.param_tests.serialization.TestSummaryWriter; +import at.gv.egiz.param_tests.testinfo.TestInfo; +import at.gv.egiz.param_tests.testinfo.TestVerdict; + +/** + * Concrete implementation of the <code>TestSummaryWriter</code>, which creates + * HTML output and uses the Twitter-bootstrap framework. + * + * @author mtappler + * + */ +public class HTMLTestSummaryWriter implements TestSummaryWriter { + + /** + * the location of the test directory + */ + private String testDir; + /** + * the print writer which is used for writing + */ + private PrintWriter pw; + /** + * the logger for this class + */ + private static final Logger logger = LoggerFactory + .getLogger(HTMLTestSummaryWriter.class); + + /** + * Constructor which sets the test directory. + * + * @param testDir + * location of the test directory + */ + public HTMLTestSummaryWriter(String testDir) { + this.testDir = testDir; + } + + public void writeHeader() { + if (pw == null) + return; + pw.println("<!doctype html>"); + pw.println("<html>"); + pw.println("<head>"); + pw.println("<title>Summary of test results</title>"); + pw.println("<meta charset=\"UTF-8\">"); + pw.println("<link rel=\"stylesheet\" type=\"text/css\" href=\"css/bootstrap.min.css\">"); + pw.println("<link rel=\"stylesheet\" type=\"text/css\" href=\"../css/bootstrap-theme.min.css\">"); + pw.println("</head>"); + pw.println("<body>"); + pw.println("<div class=\"container\">"); + pw.println("<div class=\"page-header\">"); + pw.println("<h1>Test result summary</h1>"); + pw.println("</div>"); + pw.println("<table class=\"table table-bordered table-striped\">"); + pw.println("<thead>"); + pw.println("<tr>"); + pw.println("<th>Test name</th>"); + pw.println("<th>Test directory</th>"); + pw.println("<th>Test type</th>"); + pw.println("<th>Verdict</th>"); + pw.println("</tr>"); + pw.println("</thead>"); + + } + + public void writeSummaryOfTest(TestInfo tInfo, String testType) { + if (pw == null) + return; + pw.println("<tr>"); + pw.println(String.format( + "<td><a href=\"%s/test_result.html\">%s</a></td>", tInfo + .getBaseTestData().getTestDirectory(), tInfo + .getBaseTestData().getTestName())); + pw.println(String.format("<td>%s</td>", tInfo.getBaseTestData() + .getTestDirectory())); + pw.println(String.format("<td>%s</td>", testType)); + pw.println(String.format("<td>%s</td>", + verdictToLabel(tInfo.getVerdict()))); + pw.println("</tr>"); + } + + // intentionally package protected + /** + * Static method for creating bootstrap label for a test verdict. Since it + * is technology dependent (HTML + bootstrap) it is defined as package + * protected. + * + * @param verdict + * the verdict of a test + * @return HTML-string for a verdict label + */ + static String verdictToLabel(TestVerdict verdict) { + switch (verdict) { + case FAILED: + return "<span class=\"label label-danger\">Fail</span>"; + case INCONCLUSIVE: + return "<span class=\"label label-warning\">Inconclusive</span>"; + case SUCCEEDED: + return "<span class=\"label label-success\">Success</span>"; + default: + return "<span class=\"label label-default\">Unknown</span>"; + } + } + + public void writeFooter() { + if (pw == null) + return; + + pw.println("</table>"); + pw.println("<script src=\"https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js\">" + + "</script>"); + pw.println("<script src=\"js/bootstrap.min.js\"></script>"); + pw.println("</div>"); + pw.println("</body>"); + pw.println("</html>"); + } + + public void init() { + OutputStream os; + try { + os = new FileOutputStream(testDir + "/index.html"); + pw = new PrintWriter(new OutputStreamWriter(os, "UTF8")); + } catch (FileNotFoundException e) { + logger.debug("Could not find output file, not writing any summary", + e); + } catch (UnsupportedEncodingException e) { + logger.debug("Used unsupported encoding for writing summary file.", + e); + } + + } + + public void close() { + if (pw != null) + pw.close(); + } + +} diff --git a/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/serialization/html/PDFAHTMLSerizalier.java b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/serialization/html/PDFAHTMLSerizalier.java new file mode 100644 index 00000000..fdaa80fa --- /dev/null +++ b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/serialization/html/PDFAHTMLSerizalier.java @@ -0,0 +1,147 @@ +package at.gv.egiz.param_tests.serialization.html; + +import java.io.PrintWriter; +import java.util.List; + +import org.apache.commons.lang3.tuple.Pair; +import org.apache.pdfbox.preflight.ValidationResult; +import org.apache.pdfbox.preflight.ValidationResult.ValidationError; + +import at.gv.egiz.param_tests.serialization.TestInfoSerializer; +import at.gv.egiz.param_tests.testinfo.PDFATestInfo; + +/** + * Concrete test information serializer for PDF-A conformance tests using HTML + * and Twitter-bootstrap. + * + * @author mtappler + * + */ +public class PDFAHTMLSerizalier extends HTMLSerializer<PDFATestInfo> { + + /** + * the test information object which is serialized, it is the same as + * <code>TestInfoSerializer.baseTestInfo</code> + */ + private PDFATestInfo testInfo; + + /** + * Default contructor used for registering it as prototype. + */ + public PDFAHTMLSerizalier() { + this(null); + } + + /** + * Package protected constructor used for cloning + * + * @param testInfo + */ + PDFAHTMLSerizalier(PDFATestInfo testInfo) { + this.testInfo = testInfo; + } + + @Override + public PDFATestInfo createTestInfo() { + testInfo = new PDFATestInfo(); + baseTestInfo = testInfo; + return testInfo; + } + + @Override + public TestInfoSerializer<PDFATestInfo> cloneSerializer() { + return new PDFAHTMLSerizalier(testInfo); + } + + @Override + protected void writeTestData(PrintWriter pw) { + pw.println("<h2>Validation status before signing</h2>"); + writeValidationResult(pw, testInfo.getResultBeforeSign()); + if (testInfo.getResultAfterSign() != null) { + pw.println("<h2>Validation status after signing</h2>"); + writeValidationResult(pw, testInfo.getResultAfterSign()); + } + } + + /** + * This method writes the validation result to the given print writer, i.e. + * an information about the success of the validation or an exception which + * was thrown during the validation or a list of validation errors. + * + * @param pw + * the <code>PrintWriter</code>-object to which the HTML-code is + * written + * @param validationResult + * the pair which defines the result of a validation + */ + private void writeValidationResult(PrintWriter pw, + Pair<ValidationResult, Throwable> validationResult) { + if (validationResult.getRight() != null) { + StringBuilder exceptionString = new StringBuilder(); + exceptionString + .append("p>An exception happened during the validation process:</p>"); + exceptionString.append(createExceptionDataString(validationResult + .getRight())); + writePanel(pw, "Validation exception details", + exceptionString.toString()); + } + if (validationResult.getLeft() != null) { + + StringBuilder conformanceString = new StringBuilder(); + if (validationResult.getLeft().isValid()) { + conformanceString + .append("<p>The document conforms to the PDF-A standard.</p>"); + } else { + List<ValidationError> errors = validationResult.getLeft() + .getErrorsList(); + conformanceString.append("<p>With to the PDF-A standard, " + + "the document contains the following errors:</p>"); + conformanceString + .append("<table class=\"table table-bordered table-striped\">"); + conformanceString.append("<thead>"); + conformanceString.append("<tr>"); + conformanceString.append("<th>Error code</th>"); + conformanceString.append("<th>Error details</th>"); + conformanceString.append("<th>Warning</th>"); + conformanceString.append("</tr>"); + conformanceString.append("</thead>"); + for (ValidationError error : errors) { + writeValidationError(conformanceString, error); + } + conformanceString.append("</table>"); + } + writePanel(pw, "PDF-A conformance", conformanceString.toString()); + } + } + + /** + * Helper method for writing a table line for a single validation error. + * + * @param conformanceString + * the string builder to which the table line is appended + * @param error + * the error which should be serialized + */ + private void writeValidationError(StringBuilder conformanceString, + ValidationError error) { + conformanceString.append("<tr>"); + conformanceString.append(String.format("<td>%s</td>", + error.getErrorCode())); + conformanceString.append(String.format("<td>%s</td>", + error.getDetails())); + conformanceString.append(String.format("<td>%s</td>", + error.isWarning() ? "x" : "")); + conformanceString.append("</tr>"); + } + + @Override + public String getTestType() { + return "PDF-A"; + } + + @Override + protected void writeTestSpecificParameters(PrintWriter pw) { + // intentionally left blank, no pdf-a specific parameters + } + +} diff --git a/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/serialization/html/SignaturePositionHTMLSerializer.java b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/serialization/html/SignaturePositionHTMLSerializer.java new file mode 100644 index 00000000..6cebb9ef --- /dev/null +++ b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/serialization/html/SignaturePositionHTMLSerializer.java @@ -0,0 +1,176 @@ +package at.gv.egiz.param_tests.serialization.html; + +import java.awt.Rectangle; +import java.io.PrintWriter; +import java.util.List; + +import at.gv.egiz.param_tests.serialization.TestInfoSerializer; +import at.gv.egiz.param_tests.testinfo.SignaturePositionTestInfo; +import at.gv.egiz.param_tests.testinfo.SignaturePositionTestInfo.SignaturePositionParameters; + +/** + * Concrete test information serializer for signature position tests using HTML + * and Twitter-bootstrap. + * + * @author mtappler + * + */ +public class SignaturePositionHTMLSerializer extends + HTMLSerializer<SignaturePositionTestInfo> { + /** + * the test information object which is serialized, it is the same as + * <code>TestInfoSerializer.baseTestInfo</code> + */ + private SignaturePositionTestInfo testInfo; + + /** + * Default contructor used for registering it as prototype. + */ + public SignaturePositionHTMLSerializer() { + this(null); + } + + /** + * Package protected constructor used for cloning + * + * @param testInfo + */ + SignaturePositionHTMLSerializer(SignaturePositionTestInfo testInfo) { + this.testInfo = testInfo; + } + + @Override + public SignaturePositionTestInfo createTestInfo() { + testInfo = new SignaturePositionTestInfo(); + baseTestInfo = testInfo; + return testInfo; + } + + @Override + public TestInfoSerializer<SignaturePositionTestInfo> cloneSerializer() { + return new SignaturePositionHTMLSerializer(testInfo); + } + + @Override + protected void writeTestData(PrintWriter pw) { + if (testInfo.getAdditionParameters().isCaptureReferenceImage()) { + writeTestDataCapture(pw); + } else { + writeTestDataTest(pw); + } + } + + /** + * Method for writing test data if only a reference image was captured + * + * @param pw + * object which should be used for writing + */ + private void writeTestDataCapture(PrintWriter pw) { + pw.println("<h2>Captured reference image</h2>"); + writePanel( + pw, + "Reference image", + getImageString(testInfo.getAdditionParameters() + .getRefImageFileName(), "reference image")); + if (testInfo.getRefImageIgnored() != null) { + writePanel( + pw, + "Reference image with ignored areas", + getImageString(testInfo.getRefImageIgnored(), + "reference image (ignored)")); + } + } + + /** + * Method for writing test data if an actual signature position test was + * performed. + * + * @param pw + * object which should be used for writing + */ + private void writeTestDataTest(PrintWriter pw) { + pw.println("<h2>Image data captured during test</h2>"); + writePanel( + pw, + "Reference image with ignored areas", + getImageString(testInfo.getRefImageIgnored(), + "reference image (ignored)")); + writePanel( + pw, + "Signature page image with ignored areas", + getImageString(testInfo.getSigPageImageIgnored(), + "signature page image (ignored)")); + writePanel(pw, "Difference image", + getImageString(testInfo.getDiffImage(), "difference image")); + } + + /** + * This method creates HTML-image-tag for an image file. + * + * @param imageFileName + * location of the image file + * @param altText + * the alternative text + * @return HTML-image-tag + */ + private String getImageString(String imageFileName, String altText) { + String imageString; + if (imageFileName != null) + imageString = String.format( + "<img class=\"img-responsive\" src=\"%s\" alt=\"%s\">", + imageFileName, altText); + else + imageString = "<p>This image was not captured correctly. " + + "The test may have been aborted before because of an IO error.</p>"; + return imageString; + } + + @Override + public String getTestType() { + return "Signature Position"; + } + + @Override + protected void writeTestSpecificParameters(PrintWriter pw) { + + SignaturePositionParameters additionalParameters = testInfo + .getAdditionParameters(); + StringBuilder panelBody = new StringBuilder(); + + panelBody.append("<dl>"); + panelBody.append(createDescription("Signature positioning string", + additionalParameters.getPositionString())); + panelBody.append(createDescription("Signature page number", + Integer.toString(additionalParameters.getSigPageNumber()))); + panelBody.append(createDescription("Reference image file name", + additionalParameters.getRefImageFileName())); + panelBody.append(createDescription("Ignored Areas", + createIgnoredAreasDescription(additionalParameters + .getIgnoredAreas()))); + panelBody.append("</dl>"); + writePanel(pw, "Test specific parameters", panelBody.toString()); + } + + /** + * This method creates an unordered HTML list for the ignored areas for a + * signature position test. + * + * @param ignoredAreas + * list of ignored areas + * @return HTML-string representing the ignored areas + */ + private String createIgnoredAreasDescription(List<Rectangle> ignoredAreas) { + StringBuilder sb = new StringBuilder(); + sb.append("<ul>"); + for (Rectangle r : ignoredAreas) { + sb.append("<li>"); + sb.append(String.format("x:%d y:%d width:%d height:%d", r.x, r.y, + r.width, r.height)); + sb.append("</li>"); + } + sb.append("</ul>"); + return sb.toString(); + } + +} diff --git a/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/testinfo/PDFATestInfo.java b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/testinfo/PDFATestInfo.java new file mode 100644 index 00000000..a5f7c190 --- /dev/null +++ b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/testinfo/PDFATestInfo.java @@ -0,0 +1,41 @@ +package at.gv.egiz.param_tests.testinfo; + +import org.apache.commons.lang3.tuple.Pair; +import org.apache.pdfbox.preflight.ValidationResult; + +/** + * Test information class for PDF-A conformance tests. + * + * @author mtappler + * + */ +public class PDFATestInfo extends TestInfo { + + /** + * the validation result before signing for the input PDF file + */ + private Pair<ValidationResult, Throwable> resultBeforeSign; + /** + * the validation result after signing for the output PDF file + */ + private Pair<ValidationResult, Throwable> resultAfterSign; + + public Pair<ValidationResult, Throwable> getResultBeforeSign() { + return resultBeforeSign; + } + + public void setResultBeforeSign( + Pair<ValidationResult, Throwable> resultBeforeSign) { + this.resultBeforeSign = resultBeforeSign; + } + + public Pair<ValidationResult, Throwable> getResultAfterSign() { + return resultAfterSign; + } + + public void setResultAfterSign( + Pair<ValidationResult, Throwable> resultAfterSign) { + this.resultAfterSign = resultAfterSign; + } + +} diff --git a/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/testinfo/SignaturePositionTestInfo.java b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/testinfo/SignaturePositionTestInfo.java new file mode 100644 index 00000000..04b75edc --- /dev/null +++ b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/testinfo/SignaturePositionTestInfo.java @@ -0,0 +1,138 @@ +package at.gv.egiz.param_tests.testinfo; + +import java.awt.Rectangle; +import java.util.List; + +/** + * Test information class for signature position test. + * + * @author mtappler + * + */ +public class SignaturePositionTestInfo extends TestInfo { + /** + * Class containing attributes for non-standard parameters of signature + * position tests. + * + * @author mtappler + * + */ + public static class SignaturePositionParameters { + /** + * positioning string which specifies the position of the signature + * block + */ + private String positionString; + /** + * The page number of the page, which shows the signature block. + */ + private int sigPageNumber; + /** + * A list of rectangular areas, which will be ignored for image + * comparison + */ + private List<Rectangle> ignoredAreas; + /** + * the file name of the reference file for image comparison + */ + private String refImageFileName; + /** + * if set to true, a reference image is captured during the test, but no + * actual comparison will be performed + */ + private boolean captureReferenceImage; + + public String getPositionString() { + return positionString; + } + + public void setPositionString(String positionString) { + this.positionString = positionString; + } + + public int getSigPageNumber() { + return sigPageNumber; + } + + public void setSigPageNumber(int sigPageNumber) { + this.sigPageNumber = sigPageNumber; + } + + public List<Rectangle> getIgnoredAreas() { + return ignoredAreas; + } + + public void setIgnoredAreas(List<Rectangle> ignoredAreas) { + this.ignoredAreas = ignoredAreas; + } + + public String getRefImageFileName() { + return refImageFileName; + } + + public void setRefImageFileName(String refImageFileName) { + this.refImageFileName = refImageFileName; + } + + public boolean isCaptureReferenceImage() { + return captureReferenceImage; + } + + public void setCaptureReferenceImage(boolean captureReferenceImage) { + this.captureReferenceImage = captureReferenceImage; + } + } + + /** + * additional/non-standard parameter of signature position tests + */ + private SignaturePositionParameters additionParameters = new SignaturePositionParameters(); + + /** + * file name of the reference image with ignored areas + */ + private String refImageIgnored; + + /** + * file name of the signature page image with ignored areas + */ + private String sigPageImageIgnored; + + /** + * file name of difference image + */ + private String diffImage; + + public String getRefImageIgnored() { + return refImageIgnored; + } + + public String getSigPageImageIgnored() { + return sigPageImageIgnored; + } + + public String getDiffImage() { + return diffImage; + } + + public SignaturePositionParameters getAdditionParameters() { + return additionParameters; + } + + public void setAdditionParameters( + SignaturePositionParameters additionParameters) { + this.additionParameters = additionParameters; + } + + public void setRefImageIgnored(String refImageIgnored) { + this.refImageIgnored = refImageIgnored; + } + + public void setSigPageImageIgnored(String sigPageImageIgnored) { + this.sigPageImageIgnored = sigPageImageIgnored; + } + + public void setDiffImage(String diffImage) { + this.diffImage = diffImage; + } +} diff --git a/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/testinfo/TestInfo.java b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/testinfo/TestInfo.java new file mode 100644 index 00000000..130cece3 --- /dev/null +++ b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/testinfo/TestInfo.java @@ -0,0 +1,76 @@ +package at.gv.egiz.param_tests.testinfo; + +import at.gv.egiz.param_tests.provider.BaseSignatureTestData; + +/** + * Abstract test information class containing information common to all tests. + * It is declared abstract to enforce that a subclass of it must be created for + * each test-type, because serializer prototypes are registered for + * <code>TestInfo</code>-subclasses. + * + * @author mtappler + * + */ +public abstract class TestInfo { + /** + * signature test parameters common to all tests + */ + private BaseSignatureTestData baseTestData; + /** + * the verdict of a test + */ + private TestVerdict verdict = TestVerdict.UNKNOWN; + /** + * the cause for failure, non-null if the verdict is inconclusive or fail + */ + private Throwable failCause; + /** + * standard output data written during the test + */ + private String stdOut; + /** + * standard error data written during the test + */ + private String stdErr; + + public void setBaseTestData(BaseSignatureTestData baseTestData) { + this.baseTestData = baseTestData; + } + + public Throwable getFailCause() { + return failCause; + } + + public BaseSignatureTestData getBaseTestData() { + return this.baseTestData; + } + + public TestVerdict getVerdict() { + return verdict; + } + + public String getStdOut() { + return stdOut; + } + + public void setVerdict(TestVerdict result) { + this.verdict = result; + } + + public void setFailCause(Throwable e) { + this.failCause = e; + } + + public void setStdOut(String stdOutFromTest) { + this.stdOut = stdOutFromTest; + } + + public void setStdErr(String stdErrFromTest) { + this.stdErr = stdErrFromTest; + } + + public String getStdErr() { + return stdErr; + } + +} diff --git a/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/testinfo/TestVerdict.java b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/testinfo/TestVerdict.java new file mode 100644 index 00000000..0fc31a6a --- /dev/null +++ b/pdf-as-tests/src/test/java/at/gv/egiz/param_tests/testinfo/TestVerdict.java @@ -0,0 +1,10 @@ +package at.gv.egiz.param_tests.testinfo; + +/** + * Enum defining constants for test verdicts. + * @author mtappler + * + */ +public enum TestVerdict { + UNKNOWN, INCONCLUSIVE, FAILED, SUCCEEDED +} diff --git a/pdf-as-tests/src/test/java/at/gv/egiz/pdfas/tests/DummyTest.java b/pdf-as-tests/src/test/java/at/gv/egiz/pdfas/tests/DummyTest.java new file mode 100644 index 00000000..a5564fce --- /dev/null +++ b/pdf-as-tests/src/test/java/at/gv/egiz/pdfas/tests/DummyTest.java @@ -0,0 +1,9 @@ +package at.gv.egiz.pdfas.tests; + +import org.junit.Test; + +public class DummyTest { + //@Test + //public void dummyTest() { + //} +} diff --git a/pdf-as-tests/src/test/resources/log4j.properties b/pdf-as-tests/src/test/resources/log4j.properties new file mode 100644 index 00000000..696db3ef --- /dev/null +++ b/pdf-as-tests/src/test/resources/log4j.properties @@ -0,0 +1,15 @@ +# Set root logger level to DEBUG and its only appender to A1. +log4j.rootLogger=INFO, A1 + +log4j.logger.at.gv.egiz=DEBUG +#log4j.A1.at.gv.egiz=true + +log4j.logger.developer=DEBUG +#log4j.A1.developer=true + +# A1 is set to be a ConsoleAppender. +log4j.appender.A1=org.apache.log4j.ConsoleAppender + +# A1 uses PatternLayout. +log4j.appender.A1.layout=org.apache.log4j.PatternLayout +log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
\ No newline at end of file |