| /* |
| * 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.jmeter.protocol.http.parser; |
| |
| import static org.junit.jupiter.api.Assertions.assertEquals; |
| import static org.junit.jupiter.api.Assertions.fail; |
| import static org.junit.jupiter.params.provider.Arguments.arguments; |
| |
| import java.io.BufferedReader; |
| import java.io.FileInputStream; |
| import java.io.FileNotFoundException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.io.Reader; |
| import java.net.URL; |
| import java.nio.charset.StandardCharsets; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Properties; |
| import java.util.Vector; |
| import java.util.stream.Collectors; |
| import java.util.stream.IntStream; |
| import java.util.stream.Stream; |
| |
| import org.apache.commons.io.IOUtils; |
| import org.apache.jmeter.junit.JMeterTestCase; |
| import org.apache.jmeter.util.JMeterUtils; |
| import org.junit.jupiter.api.Test; |
| import org.junit.jupiter.api.parallel.Isolated; |
| import org.junit.jupiter.params.ParameterizedTest; |
| import org.junit.jupiter.params.provider.Arguments; |
| import org.junit.jupiter.params.provider.MethodSource; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import com.google.common.collect.Iterators; |
| import com.google.common.collect.Lists; |
| |
| @Isolated |
| public class TestHTMLParser extends JMeterTestCase { |
| private static final Logger log = LoggerFactory.getLogger(TestHTMLParser.class); |
| |
| private static final String DEFAULT_UA = "Apache-HttpClient/4.2.6"; |
| private static final String UA_FF = "Mozilla/5.0 (Windows NT 5.1; rv:31.0) Gecko/20100101 Firefox/31.0"; |
| private static final String UA_IE55 = "Mozilla/4.0 (compatible;MSIE 5.5; Windows 98)"; |
| private static final String UA_IE6 = "Mozilla/5.0 (Windows; U; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727)"; |
| private static final String UA_IE7 = "Mozilla/5.0 (Windows; U; MSIE 7.0; Windows NT 6.0; en-US)"; |
| private static final String UA_IE8 = "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; " |
| + "GTB7.4; InfoPath.2; SV1; .NET CLR 3.3.69573; WOW64; en-US)"; |
| private static final String UA_IE9 = "Mozilla/5.0 (Windows; U; MSIE 9.0; WIndows NT 9.0; en-US))"; |
| private static final String UA_IE10 = "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)"; |
| |
| private static class StaticTestClass // Can't instantiate |
| { |
| private StaticTestClass() { |
| } |
| } |
| |
| @SuppressWarnings("ClassCanBeStatic") |
| private class TestClass // Can't instantiate |
| { |
| private TestClass() { |
| } |
| } |
| |
| private static class TestData { |
| private String fileName; |
| |
| private String baseUrl; |
| |
| private String expectedSet; |
| |
| private String expectedList; |
| |
| public String userAgent; |
| |
| /** |
| * |
| * @param htmlFileName HTML File with content |
| * @param baseUrl Base URL |
| * @param expectedSet Set of expected URLs |
| * @param expectedList List of expected URLs |
| */ |
| private TestData(String htmlFileName, String baseUrl, String expectedSet, String expectedList) { |
| this(htmlFileName, baseUrl, expectedSet, expectedList, DEFAULT_UA); |
| } |
| /** |
| * |
| * @param htmlFileName HTML File with content |
| * @param baseUrl Base URL |
| * @param expectedSet Set of expected URLs |
| * @param expectedList List of expected URLs |
| * @param userAgent User Agent |
| */ |
| private TestData(String htmlFileName, String baseUrl, String expectedSet, String expectedList, String userAgent) { |
| this.fileName = htmlFileName; |
| this.baseUrl = baseUrl; |
| this.expectedSet = expectedSet; |
| this.expectedList = expectedList; |
| this.userAgent = userAgent; |
| } |
| |
| } |
| |
| private static final String DEFAULT_JMETER_PARSER = |
| "org.apache.jmeter.protocol.http.parser.LagartoBasedHtmlParser"; |
| |
| // List of parsers to test. Should probably be derived automatically |
| private static final String[] PARSERS = { |
| "org.apache.jmeter.protocol.http.parser.JTidyHTMLParser", |
| "org.apache.jmeter.protocol.http.parser.RegexpHTMLParser", |
| DEFAULT_JMETER_PARSER, |
| "org.apache.jmeter.protocol.http.parser.JsoupBasedHtmlParser" |
| }; |
| |
| static String[] getParsers() { |
| return PARSERS; |
| } |
| |
| private static final TestData[] TESTS = new TestData[] { |
| new TestData("testfiles/HTMLParserTestCase.html", |
| "http://localhost/mydir/myfile.html", |
| "testfiles/HTMLParserTestCase.set", |
| "testfiles/HTMLParserTestCase.all"), |
| new TestData("testfiles/HTMLParserTestCaseWithBaseHRef.html", |
| "http://localhost/mydir/myfile.html", |
| "testfiles/HTMLParserTestCaseBase.set", |
| "testfiles/HTMLParserTestCaseBase.all"), |
| new TestData("testfiles/HTMLParserTestCaseWithBaseHRef2.html", |
| "http://localhost/mydir/myfile.html", |
| "testfiles/HTMLParserTestCaseBase.set", |
| "testfiles/HTMLParserTestCaseBase.all"), |
| new TestData("testfiles/HTMLParserTestCaseWithMissingBaseHRef.html", |
| "http://localhost/mydir/images/myfile.html", |
| "testfiles/HTMLParserTestCaseBase.set", |
| "testfiles/HTMLParserTestCaseBase.all"), |
| new TestData("testfiles/HTMLParserTestCase2.html", |
| "http:", "", ""), // Dummy as the file has no entries |
| new TestData("testfiles/HTMLParserTestCase3.html", |
| "http:", "", ""), // Dummy as the file has no entries |
| new TestData("testfiles/HTMLParserTestCaseWithComments.html", |
| "http://localhost/mydir/myfile.html", |
| "testfiles/HTMLParserTestCaseBase.set", |
| "testfiles/HTMLParserTestCaseBase.all"), |
| new TestData("testfiles/HTMLScript.html", |
| "http://localhost/", |
| "testfiles/HTMLScript.set", |
| "testfiles/HTMLScript.all"), |
| new TestData("testfiles/HTMLParserTestFrames.html", |
| "http://localhost/", |
| "testfiles/HTMLParserTestFrames.all", |
| "testfiles/HTMLParserTestFrames.all"), |
| // Relative filenames |
| new TestData("testfiles/HTMLParserTestFile_2.html", |
| "file:HTMLParserTestFile_2.html", |
| "testfiles/HTMLParserTestFile_2.all", |
| "testfiles/HTMLParserTestFile_2.all"), |
| }; |
| |
| |
| private static final TestData[] SPECIFIC_PARSER_TESTS = new TestData[] { |
| new TestData("testfiles/HTMLParserTestCaseWithConditional1.html", |
| "http://localhost/mydir/myfile.html", |
| null, |
| "testfiles/HTMLParserTestCaseWithConditional1_FF.all", |
| UA_FF), |
| new TestData("testfiles/HTMLParserTestCaseWithConditional1.html", |
| "http://localhost/mydir/myfile.html", |
| null, |
| "testfiles/HTMLParserTestCaseWithConditional1_IE6.all", |
| UA_IE6), |
| new TestData("testfiles/HTMLParserTestCaseWithConditional1.html", |
| "http://localhost/mydir/myfile.html", |
| null, |
| "testfiles/HTMLParserTestCaseWithConditional1_IE7.all", |
| UA_IE7), |
| new TestData("testfiles/HTMLParserTestCaseWithConditional1.html", |
| "http://localhost/mydir/myfile.html", |
| null, |
| "testfiles/HTMLParserTestCaseWithConditional1_IE8.all", |
| UA_IE8), |
| new TestData("testfiles/HTMLParserTestCaseWithConditional1.html", |
| "http://localhost/mydir/myfile.html", |
| null, |
| "testfiles/HTMLParserTestCaseWithConditional1_IE8.all", |
| UA_IE8), |
| |
| // FF gets mixed up by nested comments |
| new TestData("testfiles/HTMLParserTestCaseWithConditional2.html", |
| "http://localhost/mydir/myfile.html", |
| null, |
| "testfiles/HTMLParserTestCaseWithConditional2_FF.all", |
| UA_FF), |
| |
| new TestData("testfiles/HTMLParserTestCaseWithConditional2.html", |
| "http://localhost/mydir/myfile.html", |
| null, |
| "testfiles/HTMLParserTestCaseWithConditional2_IE7.all", |
| UA_IE7), |
| new TestData("testfiles/HTMLParserTestCaseWithConditional2.html", |
| "http://localhost/mydir/myfile.html", |
| null, |
| "testfiles/HTMLParserTestCaseWithConditional2_IE8.all", |
| UA_IE8), |
| new TestData("testfiles/HTMLParserTestCaseWithConditional2.html", |
| "http://localhost/mydir/myfile.html", |
| null, |
| "testfiles/HTMLParserTestCaseWithConditional2_IE9.all", |
| UA_IE9), |
| new TestData("testfiles/HTMLParserTestCaseWithConditional3.html", |
| "http://localhost/mydir/myfile.html", |
| null, |
| "testfiles/HTMLParserTestCaseWithConditional3_FF.all", |
| UA_FF), |
| new TestData("testfiles/HTMLParserTestCaseWithConditional3.html", |
| "http://localhost/mydir/myfile.html", |
| null, |
| "testfiles/HTMLParserTestCaseWithConditional3_IE10.all", |
| UA_IE10), |
| new TestData("testfiles/HTMLParserTestCaseWithConditional3.html", |
| "http://localhost/mydir/myfile.html", |
| null, |
| "testfiles/HTMLParserTestCaseWithConditional3_IE55.all", |
| UA_IE55), |
| new TestData("testfiles/HTMLParserTestCaseWithConditional3.html", |
| "http://localhost/mydir/myfile.html", |
| null, |
| "testfiles/HTMLParserTestCaseWithConditional3_IE6.all", |
| UA_IE6) |
| }; |
| |
| static Stream<Arguments> parsersAndTestNumbers() { |
| return Stream.of(PARSERS) |
| .flatMap(parser -> IntStream.range(0, TESTS.length) |
| .mapToObj(testNumber -> arguments(parser, testNumber))); |
| } |
| |
| static Stream<Arguments> specificParserTests() { |
| return IntStream.range(0, SPECIFIC_PARSER_TESTS.length) |
| .mapToObj(testNumber -> arguments(DEFAULT_JMETER_PARSER, testNumber)); |
| } |
| |
| // Test if can instantiate parser using property name |
| @ParameterizedTest |
| @MethodSource("getParsers") |
| public void testParserProperty(String parserName) throws Exception { |
| Properties p = JMeterUtils.getJMeterProperties(); |
| if (p == null) { |
| p = JMeterUtils.getProperties("jmeter.properties"); |
| } |
| p.setProperty(HTMLParser.PARSER_CLASSNAME, parserName); |
| BaseParser.getParser(p.getProperty(HTMLParser.PARSER_CLASSNAME)); |
| } |
| |
| @Test |
| public void testDefaultParser() throws Exception { |
| BaseParser.getParser(JMeterUtils.getPropDefault(HTMLParser.PARSER_CLASSNAME, HTMLParser.DEFAULT_PARSER)); |
| } |
| |
| @Test |
| public void testParserDefault() throws Exception { |
| BaseParser.getParser(HTMLParser.DEFAULT_PARSER); |
| } |
| |
| @Test |
| public void testParserMissing() throws Exception { |
| try { |
| BaseParser.getParser("no.such.parser"); |
| fail("Should not have been able to create the parser"); |
| } catch (LinkExtractorParseException e) { |
| if (!(e.getCause() instanceof ClassNotFoundException)) { |
| throw e; |
| } |
| } |
| } |
| |
| @Test |
| public void testNotParser() throws Exception { |
| try { |
| HTMLParser.getParser("java.lang.String"); |
| fail("Should not have been able to create the parser"); |
| } catch (LinkExtractorParseException e) { |
| if (e.getCause() instanceof ClassCastException) { |
| return; |
| } |
| throw e; |
| } |
| } |
| |
| @Test |
| public void testNotCreatable() throws Exception { |
| try { |
| HTMLParser.getParser(TestClass.class.getName()); |
| fail("Should not have been able to create the parser"); |
| } catch (LinkExtractorParseException e) { |
| if (e.getCause() instanceof ReflectiveOperationException) { |
| return; |
| } |
| throw e; |
| } |
| } |
| |
| @Test |
| public void testNotCreatableStatic() throws Exception { |
| try { |
| HTMLParser.getParser(StaticTestClass.class.getName()); |
| fail("Should not have been able to create the parser"); |
| } catch (LinkExtractorParseException e) { |
| if (e.getCause() instanceof ClassCastException) { |
| return; |
| } |
| if (e.getCause() instanceof IllegalAccessException) { |
| return; |
| } |
| throw e; |
| } |
| } |
| |
| @ParameterizedTest |
| @MethodSource("parsersAndTestNumbers") |
| public void testParserSet(String parserName, int testNumber) throws Exception { |
| HTMLParser p = (HTMLParser) BaseParser.getParser(parserName); |
| filetest(p, TESTS[testNumber].fileName, TESTS[testNumber].baseUrl, TESTS[testNumber].expectedSet, null, |
| false, TESTS[testNumber].userAgent); |
| } |
| |
| @SuppressWarnings("JdkObsolete") |
| @ParameterizedTest |
| @MethodSource("parsersAndTestNumbers") |
| public void testParserList(String parserName, int testNumber) throws Exception { |
| HTMLParser p = (HTMLParser) BaseParser.getParser(parserName); |
| filetest(p, TESTS[testNumber].fileName, TESTS[testNumber].baseUrl, TESTS[testNumber].expectedList, |
| new Vector<>(), true, TESTS[testNumber].userAgent); |
| } |
| |
| @ParameterizedTest |
| @MethodSource("specificParserTests") |
| public void testSpecificParserList(String parserName, int testNumber) throws Exception { |
| HTMLParser p = (HTMLParser) BaseParser.getParser(parserName); |
| filetest(p, SPECIFIC_PARSER_TESTS[testNumber].fileName, |
| SPECIFIC_PARSER_TESTS[testNumber].baseUrl, |
| SPECIFIC_PARSER_TESTS[testNumber].expectedList, |
| new ArrayList<>(), true, |
| SPECIFIC_PARSER_TESTS[testNumber].userAgent); |
| } |
| |
| |
| @SuppressWarnings("URLEqualsHashCode") |
| private static void filetest(HTMLParser p, String file, String url, String resultFile, Collection<URLString> c, |
| boolean orderMatters, // Does the order matter? |
| String userAgent) |
| throws Exception { |
| String parserName = p.getClass().getName().substring("org.apache.jmeter.protocol.http.parser.".length()); |
| String fname = file.substring(file.indexOf('/')+1); |
| log.debug("file {}", file); |
| byte[] buffer = IOUtils.toByteArray(getInputStream(file)); |
| Iterator<URL> result; |
| if (c == null) { |
| result = p.getEmbeddedResourceURLs(userAgent, buffer, new URL(url), System.getProperty("file.encoding")); |
| } else { |
| result = p.getEmbeddedResourceURLs(userAgent, buffer, new URL(url), c,System.getProperty("file.encoding")); |
| } |
| List<String> actual = Lists.newArrayList(Iterators.transform(result, Object::toString)); |
| /* |
| * TODO: Exact ordering is only required for some tests; change the |
| * comparison to do a set compare where necessary. |
| */ |
| List<String> expected = getFile(resultFile); |
| if (!orderMatters) { |
| Collections.sort(expected); |
| Collections.sort(actual); |
| } |
| |
| assertEquals(expected, actual, "userAgent=" + userAgent + ", fname=" + fname + ", parserName=" + parserName); |
| } |
| |
| // Get expected results as a List |
| private static List<String> getFile(String file) throws Exception { |
| if (file == null || file.isEmpty()) { |
| return Collections.emptyList(); |
| } |
| try (InputStream is = getInputStream(file); |
| Reader fr = new InputStreamReader(is, StandardCharsets.UTF_8); |
| BufferedReader br = new BufferedReader(fr)) { |
| return br.lines().collect(Collectors.toList()); |
| } |
| } |
| |
| private static InputStream getInputStream(String file) { |
| if (file.startsWith("testfiles/HTMLParserTestFile_2")) { |
| // testfiles/HTMLParserTestFile_2 is shared between unit tests and batch tests, |
| // so the file is located in root/bin/testfiles/... |
| String path = "../../../bin/" + file; |
| try { |
| return new FileInputStream(path); |
| } catch (FileNotFoundException e) { |
| throw new IllegalArgumentException("File " + file + " is not found (" + path + ")"); |
| } |
| } |
| return TestHTMLParser.class.getResourceAsStream("/" + file); |
| } |
| } |