|
30 | 30 | package com.jcabi.xml;
|
31 | 31 |
|
32 | 32 | import com.google.common.collect.Iterables;
|
33 |
| -import com.jcabi.log.Logger; |
34 | 33 | import com.jcabi.matchers.XhtmlMatchers;
|
35 | 34 | import java.io.ByteArrayInputStream;
|
36 | 35 | import java.io.File;
|
|
50 | 49 | import java.util.concurrent.Executors;
|
51 | 50 | import java.util.concurrent.TimeUnit;
|
52 | 51 | import java.util.concurrent.atomic.AtomicInteger;
|
| 52 | +import java.util.stream.Collectors; |
53 | 53 | import java.util.stream.IntStream;
|
54 | 54 | import javax.xml.parsers.DocumentBuilderFactory;
|
55 |
| -import javax.xml.parsers.ParserConfigurationException; |
56 | 55 | import org.apache.commons.lang3.RandomStringUtils;
|
57 | 56 | import org.apache.commons.lang3.StringUtils;
|
58 | 57 | import org.cactoos.io.ResourceOf;
|
|
69 | 68 | import org.w3c.dom.Document;
|
70 | 69 | import org.w3c.dom.Element;
|
71 | 70 | import org.w3c.dom.Node;
|
72 |
| -import org.xml.sax.InputSource; |
73 |
| -import org.xml.sax.SAXException; |
74 | 71 | import org.xml.sax.SAXParseException;
|
75 | 72 |
|
76 | 73 | /**
|
@@ -680,102 +677,81 @@ void validatesMultipleXmlsInThreads() throws Exception {
|
680 | 677 | service.shutdownNow();
|
681 | 678 | }
|
682 | 679 |
|
683 |
| - |
684 |
| - @Test |
685 |
| - @Disabled |
686 |
| - void createsManyXmlDocuments() throws ParserConfigurationException, IOException, SAXException { |
687 |
| - final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); |
688 |
| - String xml = this.large(); |
689 |
| - final long startSimple = System.nanoTime(); |
690 |
| - final String expected = "root"; |
691 |
| - for (int i = 0; i < 10_000; ++i) { |
692 |
| - final Document parse = factory.newDocumentBuilder() |
693 |
| - .parse(new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8))); |
694 |
| - final String actual = parse.getFirstChild().getNodeName(); |
695 |
| - MatcherAssert.assertThat( |
696 |
| - actual, |
697 |
| - Matchers.equalTo(expected) |
698 |
| - ); |
699 |
| - } |
700 |
| - final long endSimple = System.nanoTime(); |
701 |
| - System.out.println( |
702 |
| - "Default approach to create XML timing: " + (endSimple - startSimple) / 1_000_000 + " ms"); |
703 |
| - Logger.info(this, "Time: %[ms]s", (endSimple - startSimple) / 1000); |
704 |
| - final long start = System.nanoTime(); |
705 |
| - for (int i = 0; i < 10_000; ++i) { |
706 |
| - ; |
707 |
| - final String actual = new XMLDocument( |
708 |
| - new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8))) |
709 |
| - .node() |
710 |
| - .getFirstChild().getNodeName(); |
711 |
| - MatcherAssert.assertThat( |
712 |
| - actual, |
713 |
| - Matchers.equalTo(expected) |
714 |
| - ); |
715 |
| - } |
716 |
| - final long end = System.nanoTime(); |
717 |
| - System.out.println( |
718 |
| - "jcabi-xml approach to create XML timing: " + (end - start) / 1_000_000 + " ms" |
719 |
| - ); |
720 |
| - } |
721 |
| - |
722 |
| - |
723 |
| - // ~ 1.6 |
| 680 | + /** |
| 681 | + * This test is disabled because it is a performance test that might be flaky. |
| 682 | + * @param temp Temporary directory. |
| 683 | + * @throws IOException If something goes wrong. |
| 684 | + */ |
724 | 685 | @RepeatedTest(10)
|
725 | 686 | @Disabled
|
726 |
| - void createsXmlFromFile( |
727 |
| - @TempDir final Path temp |
728 |
| - ) throws IOException, ParserConfigurationException, SAXException { |
| 687 | + void createsXmlFromFile(@TempDir final Path temp) throws IOException { |
729 | 688 | final Path xml = temp.resolve("test.xml");
|
730 |
| - String content = this.large(); |
731 |
| - Files.write(xml, content.getBytes(StandardCharsets.UTF_8)); |
732 |
| - final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); |
733 |
| - final long startSimple = System.nanoTime(); |
734 |
| - final String expected = "root"; |
735 |
| - for (int i = 0; i < 10_000; ++i) { |
736 |
| - final Document parse = factory.newDocumentBuilder().parse(xml.toFile()); |
737 |
| - final String actual = parse.getFirstChild().getNodeName(); |
738 |
| - MatcherAssert.assertThat( |
739 |
| - actual, |
740 |
| - Matchers.equalTo(expected) |
741 |
| - ); |
742 |
| - } |
743 |
| - final long endSimple = System.nanoTime(); |
744 |
| - final double simple = (endSimple - startSimple) / 1_000_000.0d; |
745 |
| - System.out.println( |
746 |
| - "Default approach to create XML timing: " + simple + " ms"); |
747 |
| - final long start = System.nanoTime(); |
748 |
| - for (int i = 0; i < 10_000; ++i) { |
749 |
| - final String actual = new XMLDocument(xml).node() |
750 |
| - .getFirstChild().getNodeName(); |
751 |
| - MatcherAssert.assertThat( |
752 |
| - actual, |
753 |
| - Matchers.equalTo(expected) |
754 |
| - ); |
755 |
| - } |
756 |
| - final long end = System.nanoTime(); |
757 |
| - final double actual = (end - start) / 1_000_000.0d; |
758 |
| - System.out.println( |
759 |
| - "jcabi-xml approach to create XML timing: " + actual + " ms" |
| 689 | + Files.write(xml, XMLDocumentTest.large().getBytes(StandardCharsets.UTF_8)); |
| 690 | + final long clear = XMLDocumentTest.measure( |
| 691 | + () -> DocumentBuilderFactory.newInstance() |
| 692 | + .newDocumentBuilder() |
| 693 | + .parse(xml.toFile()) |
| 694 | + .getFirstChild() |
| 695 | + .getNodeName() |
760 | 696 | );
|
761 |
| - System.out.println( |
762 |
| - "Simple approach is " + (actual / simple) + " times faster" |
| 697 | + final long wrapped = XMLDocumentTest.measure( |
| 698 | + () -> new XMLDocument(xml.toFile()).inner().getFirstChild().getNodeName() |
763 | 699 | );
|
764 |
| - |
| 700 | + MatcherAssert.assertThat( |
| 701 | + String.format( |
| 702 | + "We expect that jcabi-xml is at max 2 times slower than default approach, time spend on jcabi-xml: %d ms, time spend on default approach: %d ms", |
| 703 | + wrapped, |
| 704 | + clear |
| 705 | + ), |
| 706 | + wrapped / clear, |
| 707 | + Matchers.lessThan(2L) |
| 708 | + ); |
| 709 | + } |
| 710 | + |
| 711 | + /** |
| 712 | + * Measure the time of execution. |
| 713 | + * @param run The callable to run. |
| 714 | + * @return Time in milliseconds. |
| 715 | + * @checkstyle IllegalCatchCheck (20 lines) |
| 716 | + */ |
| 717 | + @SuppressWarnings({"PMD.AvoidCatchingGenericException", "PMD.PrematureDeclaration"}) |
| 718 | + private static long measure(final Callable<String> run) { |
| 719 | + final long start = System.nanoTime(); |
| 720 | + if (!IntStream.range(0, 1000).mapToObj( |
| 721 | + each -> { |
| 722 | + try { |
| 723 | + return run.call(); |
| 724 | + } catch (final Exception exception) { |
| 725 | + throw new IllegalStateException( |
| 726 | + String.format("Failed to run %s", run), exception |
| 727 | + ); |
| 728 | + } |
| 729 | + } |
| 730 | + ).allMatch("root"::equals)) { |
| 731 | + throw new IllegalStateException("Invalid result"); |
| 732 | + } |
| 733 | + return System.nanoTime() - start / 1_000_000; |
765 | 734 | }
|
766 | 735 |
|
767 |
| - private String large() { |
768 |
| - final StringBuilder builder = new StringBuilder( |
769 |
| - "<?xml version=\"1.0\" encoding=\"UTF-8\"?>").append("<root>"); |
770 |
| - final String payment = StringUtils.join( |
771 |
| - "<payment><id>333</id>", |
772 |
| - "<date>1-Jan-2013</date>", |
773 |
| - "<debit>test-1</debit>", |
774 |
| - "<credit>test-2</credit>", |
775 |
| - "</payment>" |
776 |
| - ); |
777 |
| - IntStream.range(0, 1_000).mapToObj(i -> payment).forEach(builder::append); |
778 |
| - return builder.append("</root>").toString(); |
| 736 | + /** |
| 737 | + * Generate large XML for tests. |
| 738 | + * @return Large XML string. |
| 739 | + */ |
| 740 | + private static String large() { |
| 741 | + return IntStream.range(0, 100) |
| 742 | + .mapToObj( |
| 743 | + i -> StringUtils.join( |
| 744 | + "<payment><id>333</id>", |
| 745 | + "<date>1-Jan-2013</date>", |
| 746 | + "<debit>test-1</debit>", |
| 747 | + "<credit>test-2</credit>", |
| 748 | + "</payment>" |
| 749 | + ) |
| 750 | + ).collect( |
| 751 | + Collectors.joining( |
| 752 | + "", "<?xml version=\"1.0\" encoding=\"UTF-8\"?><root>", "</root>" |
| 753 | + ) |
| 754 | + ); |
779 | 755 | }
|
780 | 756 |
|
781 | 757 | }
|
0 commit comments