| /* ==================================================================== |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to You under the Apache License, Version 2.0 |
| (the "License"); you may not use this file except in compliance with |
| the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| ==================================================================== */ |
| package org.apache.poi.stress; |
| |
| import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; |
| import static org.junit.jupiter.api.Assertions.assertEquals; |
| import static org.junit.jupiter.api.Assertions.assertNotNull; |
| import static org.junit.jupiter.api.Assertions.assertThrows; |
| import static org.junit.jupiter.api.Assertions.assertTrue; |
| import static org.junit.jupiter.api.Assertions.fail; |
| |
| import java.io.BufferedInputStream; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.stream.Stream; |
| |
| import org.apache.poi.POIDataSamples; |
| import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey; |
| import org.apache.tools.ant.DirectoryScanner; |
| import org.junit.jupiter.api.Assumptions; |
| import org.junit.jupiter.api.function.Executable; |
| import org.junit.jupiter.api.parallel.Execution; |
| import org.junit.jupiter.api.parallel.ExecutionMode; |
| import org.junit.jupiter.params.ParameterizedTest; |
| import org.junit.jupiter.params.provider.Arguments; |
| import org.junit.jupiter.params.provider.MethodSource; |
| import org.opentest4j.AssertionFailedError; |
| |
| /** |
| * This is an integration test which performs various actions on all stored test-files and tries |
| * to reveal problems which are introduced, but not covered (yet) by unit tests. |
| * |
| * This test looks for any file under the test-data directory and tries to do some useful |
| * processing with it based on its type. |
| * |
| * The test is implemented as a junit {@link ParameterizedTest} test, which leads |
| * to one test-method call for each file (currently around 950 files are handled). |
| * |
| * There is a mapping of extension to implementations of the interface |
| * {@link FileHandler} which defines how the file is loaded and which actions are |
| * tried with the file. |
| * |
| * The test can be expanded by adding more actions to the FileHandlers, this automatically |
| * applies the action to any such file in our test-data repository. |
| * |
| * There is also a list of files that should actually fail. |
| * |
| * Note: It is also a test-failure if a file that is expected to fail now actually works, |
| * i.e. if a bug was fixed in POI itself, the file should be removed from the expected-failures |
| * here as well! This is to ensure that files that should not work really do not work, e.g. |
| * that we do not remove expected sanity checks. |
| */ |
| // also need to set JVM parameter: -Djunit.jupiter.execution.parallel.enabled=true |
| @Execution(ExecutionMode.CONCURRENT) |
| public class TestAllFiles { |
| private static final String DEFAULT_TEST_DATA_PATH = "test-data"; |
| public static final File ROOT_DIR; |
| static { |
| File dir = new File(System.getProperty(POIDataSamples.TEST_PROPERTY, DEFAULT_TEST_DATA_PATH)); |
| if (!dir.exists()) { |
| dir = new File(System.getProperty(POIDataSamples.TEST_PROPERTY, "../" + DEFAULT_TEST_DATA_PATH)); |
| } |
| |
| ROOT_DIR = dir; |
| } |
| |
| public static final String[] SCAN_EXCLUDES = { |
| "**/.svn/**", |
| "lost+found", |
| "**/.git/**", |
| //the DocType (DTD) declaration causes this to fail |
| "**/ExternalEntityInText.docx", |
| |
| // exclude files failing on windows nodes, because of limited JCE policies |
| "document/bug53475-password-is-pass.docx", |
| "poifs/60320-protected.xlsx", |
| "poifs/protected_sha512.xlsx", |
| "poifs/60320-protected.xlsx", |
| "poifs/protected_sha512.xlsx", |
| }; |
| |
| // cheap workaround of skipping the few problematic files |
| public static final String[] SCAN_EXCLUDES_NOSCRATCHPAD = { |
| "**/.svn/**", |
| "lost+found", |
| "**/.git/**", |
| "**/ExternalEntityInText.docx", //the DocType (DTD) declaration causes this to fail |
| "**/right-to-left.xlsx", //the threaded comments in this file cause XSSF clone to fail |
| "document/word2.doc", |
| "document/cpansearch.perl.org_src_tobyink_acme-rundoc-0.001_word-lib_hello_world.docm", |
| "document/Fuzzed.doc", |
| "hpsf/Test0313rur.adm", |
| "spreadsheet/43493.xls", |
| "spreadsheet/44958.xls", |
| "spreadsheet/44958_1.xls", |
| "spreadsheet/46904.xls", |
| "spreadsheet/51832.xls", |
| "spreadsheet/60284.xls", |
| "spreadsheet/testArraysAndTables.xls", |
| "spreadsheet/testEXCEL_3.xls", |
| "spreadsheet/testEXCEL_4.xls", |
| "poifs/unknown_properties.msg", |
| "publisher/clusterfuzz-testcase-minimized-POIHPBFFuzzer-4701121678278656.pub", |
| "hsmf/clusterfuzz-testcase-minimized-POIHSMFFuzzer-4848576776503296.msg", |
| "hsmf/clusterfuzz-testcase-minimized-POIHSMFFuzzer-5336473854148608.msg", |
| "slideshow/clusterfuzz-testcase-minimized-POIHSLFFuzzer-6416153805979648.ppt", |
| "slideshow/clusterfuzz-testcase-minimized-POIHSLFFuzzer-6710128412590080.ppt", |
| "publisher/clusterfuzz-testcase-minimized-POIHPBFFuzzer-4701121678278656.pub", |
| "spreadsheet/clusterfuzz-testcase-minimized-POIHSSFFuzzer-5285517825277952.xls", |
| "spreadsheet/clusterfuzz-testcase-minimized-POIHSSFFuzzer-6322470200934400.xls", |
| "document/clusterfuzz-testcase-minimized-POIHWPFFuzzer-5418937293340672.doc", |
| "document/clusterfuzz-testcase-minimized-POIHWPFFuzzer-5440721166139392.doc", |
| "diagram/clusterfuzz-testcase-minimized-POIHDGFFuzzer-5947849161179136.vsd", |
| "spreadsheet/clusterfuzz-testcase-minimized-POIHSSFFuzzer-5436547081830400.xls", |
| "spreadsheet/clusterfuzz-testcase-minimized-POIHSSFFuzzer-4819588401201152.xls", |
| "diagram/clusterfuzz-testcase-minimized-POIVisioFuzzer-4537225637134336.vsd", |
| "spreadsheet/clusterfuzz-testcase-minimized-POIHSSFFuzzer-6537773940867072.xls", |
| "spreadsheet/clusterfuzz-testcase-minimized-POIHSSFFuzzer-4977868385681408.xls", |
| "spreadsheet/clusterfuzz-testcase-minimized-POIXSSFFuzzer-5089447305609216.xlsx", |
| "spreadsheet/clusterfuzz-testcase-minimized-POIXSSFFuzzer-5089447305609216.xlsx", |
| "spreadsheet/clusterfuzz-testcase-minimized-POIXSSFFuzzer-5089447305609216.xlsx", |
| |
| // exclude files failing on windows nodes, because of limited JCE policies |
| "document/bug53475-password-is-pass.docx", |
| "poifs/60320-protected.xlsx", |
| "poifs/protected_sha512.xlsx", |
| "poifs/60320-protected.xlsx", |
| "poifs/protected_sha512.xlsx", |
| }; |
| |
| private static final Set<String> EXPECTED_FAILURES = StressTestUtils.unmodifiableHashSet( |
| "document/truncated62886.docx" |
| ); |
| |
| public static Stream<Arguments> allfiles(String testName) throws IOException { |
| StressMap sm = new StressMap(); |
| sm.load(new File(ROOT_DIR, "spreadsheet/stress.xls")); |
| |
| boolean noScratch = Boolean.getBoolean("scratchpad.ignore"); |
| |
| DirectoryScanner scanner = new DirectoryScanner(); |
| scanner.setBasedir(ROOT_DIR); |
| scanner.setExcludes(noScratch ? SCAN_EXCLUDES_NOSCRATCHPAD : SCAN_EXCLUDES); |
| scanner.scan(); |
| |
| final List<Arguments> result = new ArrayList<>(100); |
| for (String file : scanner.getIncludedFiles()) { |
| // avoid running on files leftover from previous failed runs |
| // or being created by tests run in parallel |
| if(file.endsWith("-saved.xls") || file.endsWith("TestHPSFWritingFunctionality.doc")) { |
| continue; |
| } |
| |
| for (FileHandlerKnown handler : sm.getHandler(file)) { |
| ExcInfo info1 = sm.getExcInfo(file, testName, handler); |
| if (info1 == null || info1.isValid(testName, handler.name())) { |
| result.add(Arguments.of( |
| file, |
| handler, |
| (info1 != null) ? info1.getPassword() : null, |
| (info1 != null) ? info1.getExClazz() : null, |
| (info1 != null) ? info1.getExMessage() : null |
| )); |
| } |
| } |
| } |
| |
| return result.stream(); |
| } |
| |
| public static Stream<Arguments> extractFiles() throws IOException { |
| return allfiles("extract"); |
| } |
| |
| @ParameterizedTest(name = "Extracting - #{index} {0} {1}") |
| @MethodSource("extractFiles") |
| void handleExtracting(String file, FileHandlerKnown handler, String password, Class<? extends Throwable> exClass, String exMessage) throws IOException { |
| String threadName = Thread.currentThread().getName(); |
| try { |
| Thread.currentThread().setName("Extracting - " + file + " - " + handler); |
| if (StressTestUtils.excludeFile(file, EXPECTED_FAILURES)) |
| return; |
| |
| System.out.println("Running extractFiles on " + file); |
| FileHandler fileHandler = handler.getHandler(); |
| assertNotNull(fileHandler, "Did not find a handler for file " + file); |
| Executable exec = () -> fileHandler.handleExtracting(new File(ROOT_DIR, file)); |
| verify(file, exec, exClass, exMessage, password, fileHandler); |
| } finally { |
| Thread.currentThread().setName(threadName); |
| } |
| } |
| |
| public static Stream<Arguments> handleFiles() throws IOException { |
| return allfiles("handle"); |
| } |
| |
| @ParameterizedTest(name = "#{index} {0} {1}") |
| @MethodSource("handleFiles") |
| void handleFile(String file, FileHandlerKnown handler, String password, Class<? extends Throwable> exClass, String exMessage) throws IOException { |
| String threadName = Thread.currentThread().getName(); |
| try { |
| Thread.currentThread().setName("Handle - " + file + " - " + handler); |
| |
| // Some of the tests hang in JDK 8 due to Graphics-Rendering issues in JDK itself, |
| // therefore we do not run some tests here |
| Assumptions.assumeFalse(isJava8() && ( |
| file.endsWith("23884_defense_FINAL_OOimport_edit.ppt") |
| ), "Some files hang in JDK graphics rendering on Java 8 due to a JDK bug"); |
| |
| System.out.println("Running handleFiles on "+file); |
| FileHandler fileHandler = handler.getHandler(); |
| assertNotNull(fileHandler, "Did not find a handler for file " + file); |
| try (InputStream stream = new BufferedInputStream(new FileInputStream(new File(ROOT_DIR, file)), 64 * 1024)) { |
| Executable exec = () -> fileHandler.handleFile(stream, file); |
| verify(file, exec, exClass, exMessage, password, fileHandler); |
| } |
| } finally { |
| Thread.currentThread().setName(threadName); |
| } |
| } |
| |
| public static Stream<Arguments> handleAdditionals() throws IOException { |
| return allfiles("additional"); |
| } |
| |
| @ParameterizedTest(name = "Additional - #{index} {0} {1}") |
| @MethodSource("handleAdditionals") |
| void handleAdditional(String file, FileHandlerKnown handler, String password, Class<? extends Throwable> exClass, String exMessage) { |
| String threadName = Thread.currentThread().getName(); |
| try { |
| Thread.currentThread().setName("Additional - " + file + " - " + handler); |
| System.out.println("Running additionals on "+file); |
| FileHandler fileHandler = handler.getHandler(); |
| assertNotNull(fileHandler, "Did not find a handler for file " + file); |
| Executable exec = () -> fileHandler.handleAdditional(new File(ROOT_DIR, file)); |
| verify(file, exec, exClass, exMessage, password, fileHandler); |
| } finally { |
| Thread.currentThread().setName(threadName); |
| } |
| } |
| |
| @SuppressWarnings("unchecked") |
| private static void verify(String file, Executable exec, Class<? extends Throwable> exClass, String exMessage, String password, |
| FileHandler fileHandler) { |
| final String errPrefix = file.replace("\\", "/") + " - failed for handler " + fileHandler.getClass().getSimpleName() + ": "; |
| // this also removes the password for non encrypted files |
| Biff8EncryptionKey.setCurrentUserPassword(password); |
| if (exClass != null && AssertionFailedError.class.isAssignableFrom(exClass)) { |
| try { |
| exec.execute(); |
| fail(errPrefix + "Expected failed assertion " + exClass + " and message " + exMessage); |
| } catch (AssertionFailedError e) { |
| String actMsg = pathReplace(e.getMessage()); |
| assertEquals(exMessage, actMsg, errPrefix); |
| } catch (Throwable e) { |
| fail(errPrefix + "Unexpected exception, expected " + exClass + " and message " + exMessage, e); |
| } |
| } else if (exClass != null) { |
| Exception e = assertThrows((Class<? extends Exception>)exClass, exec, errPrefix + " expected " + exClass); |
| String actMsg = pathReplace(e.getMessage()); |
| |
| // perform special handling of NullPointerException as |
| // JDK started to add more information in some newer JDK, so |
| // it sometimes has a message and sometimes not! |
| if (NullPointerException.class.isAssignableFrom(exClass)) { |
| if (actMsg != null) { |
| assertTrue(actMsg.contains(exMessage), errPrefix + "Message: " + actMsg + " - didn't contain: " + exMessage); |
| } |
| } else { |
| // verify that message is either null for both or set for both |
| assertTrue(actMsg != null || isBlank(exMessage), |
| errPrefix + " for " + exClass + " expected message '" + exMessage + "' but had '" + actMsg + "': " + e); |
| |
| if (actMsg != null && |
| // in newer JDK versions IndexOutOfBoundsException switch from empty message |
| // to more useful content |
| // so skip the check for this type of exception if expected message is null |
| (exMessage != null || !IndexOutOfBoundsException.class.isAssignableFrom(exClass))) { |
| assertNotNull(exMessage, |
| errPrefix + "Expected message was null, but actMsg wasn't: Message: " + actMsg + ": " + e); |
| assertTrue(actMsg.contains(exMessage), |
| errPrefix + "Message: " + actMsg + " - didn't contain: " + exMessage); |
| } |
| } |
| } else { |
| assertDoesNotThrow(exec, errPrefix); |
| } |
| } |
| |
| private static boolean isBlank(final String str) { |
| if (str != null) { |
| final int strLen = str.length(); |
| for (int i = 0; i < strLen; i++) { |
| if (!Character.isWhitespace(str.charAt(i))) { |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| private static String pathReplace(String msg) { |
| if (msg == null) return null; |
| |
| // Windows path replacement |
| msg = msg.replace('\\', '/'); |
| |
| // Adjust file paths to remove unwanted file path info. |
| int filePathIndex = msg.indexOf(ROOT_DIR.toString()); |
| if (filePathIndex > 0) { |
| int testDataDirectoryIndex = msg.indexOf(DEFAULT_TEST_DATA_PATH); |
| msg = msg.substring(0, filePathIndex) + msg.substring(testDataDirectoryIndex); |
| } |
| |
| return msg; |
| } |
| |
| private static boolean isJava8() { |
| return System.getProperty("java.version").startsWith("1.8"); |
| } |
| } |