diff --git a/AGENTS.md b/AGENTS.md index 89d81ab9..b745bcd4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -11,8 +11,8 @@ LLM-based agents can accelerate development only if they respect our house rules | Requirement | Rationale | |--------------|-----------| | **British English** spelling (`organisation`, `licence`, *not* `organization`, `license`) except technical US spellings like `synchronized` | Keeps wording consistent with Chronicle's London HQ and existing docs. See the University of Oxford style guide for reference. | -| **ASCII-7 only** (code-points 0-127). Avoid smart quotes, non-breaking spaces and accented characters. | ASCII-7 survives every toolchain Chronicle uses, incl. low-latency binary wire formats that expect the 8th bit to be 0. | -| If you must show a symbol that does not exist in ASCII-7, spell it out (`micro-second`, `>=`, `:alpha:`, `:yes:`) rather than inserting Unicode. | Extended or '8-bit ASCII' variants are *not* portable and are therefore disallowed. | +| **ISO-8859-1** (code-points 0-255). Avoid smart quotes, non-breaking spaces and accented characters. | ISO-8859-1 survives every toolchain Chronicle uses, incl. low-latency binary wire formats that expect the 8th bit to be 0. | +| If you must show a symbol that does not exist in ISO-8859-1, spell it out (`micro-second`, `>=`, `:alpha:`, `:yes:`) rather than inserting Unicode. | Extended or '8-bit ASCII' variants are *not* portable and are therefore disallowed. | ## Javadoc guidelines @@ -52,8 +52,8 @@ mvn -q verify ## Project requirements -See the [Decision Log](src/main/adoc/decision-log.adoc) for the latest project decisions. -See the [Project Requirements](src/main/adoc/project-requirements.adoc) for details on project requirements. +See the [Decision Log](src/main/docs/decision-log.adoc) for the latest project decisions. +See the [Project Requirements](src/main/docs/project-requirements.adoc) for details on project requirements. ## Elevating the Workflow with Real-Time Documentation @@ -85,7 +85,7 @@ This tight loop informs the AI accurately and creates immediate clarity for all When using AI agents to assist with development, please adhere to the following guidelines: -* **Respect the Language & Character-set Policy**: Ensure all AI-generated content follows the British English and ASCII-7 guidelines outlined above. +* **Respect the Language & Character-set Policy**: Ensure all AI-generated content follows the British English and ISO-8859-1 guidelines outlined above. Focus on Clarity: AI-generated documentation should be clear and concise and add value beyond what is already present in the code or existing documentation. * **Avoid Redundancy**: Do not generate content that duplicates existing documentation or code comments unless it provides additional context or clarification. * **Review AI Outputs**: Always review AI-generated content for accuracy, relevance, and adherence to the project's documentation standards before committing it to the repository. @@ -159,4 +159,4 @@ Do not rely on indentation for list items in AsciiDoc documents. Use the followi ### Emphasis and Bold Text -In AsciiDoc, an underscore `_` is _emphasis_; `*text*` is *bold*. \ No newline at end of file +In AsciiDoc, an underscore `_` is _emphasis_; `*text*` is *bold*. diff --git a/README.adoc b/README.adoc index 434057ac..95c2ac0a 100644 --- a/README.adoc +++ b/README.adoc @@ -7,7 +7,7 @@ image:https://maven-badges.herokuapp.com/maven-central/net.openhft/chronicle-test-framework/badge.svg[Maven Central,link=https://maven-badges.herokuapp.com/maven-central/net.openhft/chronicle-test-framework] image:https://javadoc.io/badge2/net.openhft/chronicle-test-framework/javadoc.svg[Javadoc,link="https://www.javadoc.io/doc/net.openhft/chronicle-test-framework/latest/index.html"] -//image:https://javadoc-badge.appspot.com/net.openhft/chronicle-test-framework.svg?label=javadoc[JavaDoc, link=https://www.javadoc.io/doc/net.openhft/chronicle-test-framework] +// image:https://javadoc-badge.appspot.com/net.openhft/chronicle-test-framework.svg?label=javadoc[JavaDoc, link=https://www.javadoc.io/doc/net.openhft/chronicle-test-framework] image:https://img.shields.io/github/license/OpenHFT/Chronicle-Test-Framework[Licence] image:https://img.shields.io/badge/release%20notes-subscribe-brightgreen[Release Notes,link="https://chronicle.software/release-notes/"] image:https://sonarcloud.io/api/project_badges/measure?project=OpenHFT_ChronicleTestFramework&metric=alert_status[Quality Gate,link="https://sonarcloud.io/dashboard?id=OpenHFT_Chronicle-Test-Framework"] @@ -18,6 +18,17 @@ toc::[] Chronicle Test Framework provides test-data generators and API metrics tools for JUnit 4 and JUnit 5. Use the combinators to create permutations, combinations and products, or analyse your packages with the metrics builder. +== Quality Checks + +Activate the `code-review` Maven profile before sending patches: + +[source,bash] +---- +mvn -q -Pcode-review verify +---- + +The profile runs Checkstyle, SpotBugs, PMD, and JaCoCo with the shared Chronicle rules to catch regressions early. + == Permutations Permutations work like this: @@ -69,7 +80,8 @@ Stream demo() { == API Metrics Builder The API metrics feature analyses the public and internal surface of your -packages. Create the builder via `ApiMetrics.builder()` then supply the +packages. +Create the builder via `ApiMetrics.builder()` then supply the packages to scan, the metrics to apply and the accumulators that gather the results. @@ -82,9 +94,12 @@ ApiMetrics metrics = ApiMetrics.builder() .build(); ---- -The builder applies no defaults. If you omit metrics or accumulators the -result will be empty. Default metrics and accumulators are only added when you -call the relevant `addStandard` methods. Packages with `.internal.` in the +The builder applies no defaults. +If you omit metrics or accumulators the +result will be empty. +Default metrics and accumulators are only added when you +call the relevant `addStandard` methods. +Packages with `.internal.` in the name, or those ending in `.internal`, are collected in a separate internal group. @@ -231,7 +246,8 @@ Stream demo() { == API Metrics Accumulators -The framework includes a small set of predefined accumulator suppliers used by the API metrics analysis. They help to group metric counts in common ways. +The framework includes a small set of predefined accumulator suppliers used by the API metrics analysis. +They help to group metric counts in common ways. * `perMethod()` groups by the full method signature so that overloaded methods are reported separately. * `perClassAndMetric()` groups first by class and then by metric, giving a table of metric totals for each class. @@ -240,4 +256,5 @@ The framework includes a small set of predefined accumulator suppliers used by t * `PER_PACKAGE` reports a single total for each package. * `PER_METHOD_REFERENCE` groups by method name only, ignoring parameter types, so all overloads are counted as one. -The convenience factories in `Accumulator` expose the first two suppliers for general use. The others are mainly for internal analyses but may be reused when custom behaviour is needed. +The convenience factories in `Accumulator` expose the first two suppliers for general use. +The others are mainly for internal analyses but may be reused when custom behaviour is needed. diff --git a/pom.xml b/pom.xml index 2b057b7a..640718c1 100644 --- a/pom.xml +++ b/pom.xml @@ -34,6 +34,15 @@ openhft https://sonarcloud.io + 3.6.0 + 8.45.1 + 4.8.6.6 + 1.14.0 + 3.28.0 + 0.8.14 + 0.846 + 0.707 + 1.23ea6 @@ -119,25 +128,6 @@ - - org.jacoco - jacoco-maven-plugin - - - - prepare-agent - - - - report - prepare-package - - report - - - - - org.sonarsource.scanner.maven sonar-maven-plugin @@ -146,6 +136,150 @@ + + + code-review + + false + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + ${checkstyle.version} + + + com.puppycrawl.tools + checkstyle + ${puppycrawl.version} + + + net.openhft + chronicle-quality-rules + ${chronicle-quality-rules.version} + + + + + checkstyle + verify + + check + + + + + src/main/config/checkstyle.xml + true + true + warning + + + + com.github.spotbugs + spotbugs-maven-plugin + ${spotbugs.version} + + + com.h3xstream.findsecbugs + findsecbugs-plugin + ${findsecbugs.version} + + + + + spotbugs + verify + + check + + + + + Max + Low + true + src/main/config/spotbugs-exclude.xml + + + com.h3xstream.findsecbugs + findsecbugs-plugin + ${findsecbugs.version} + + + + + + org.apache.maven.plugins + maven-pmd-plugin + ${maven-pmd-plugin.version} + + + pmd + verify + + check + + + + + true + true + src/main/config/pmd-exclude.properties + + + + org.jacoco + jacoco-maven-plugin + ${jacoco-maven-plugin.version} + + + prepare-agent + + prepare-agent + + + + report + verify + + report + + + + check + verify + + check + + + + + BUNDLE + + + LINE + COVEREDRATIO + ${jacoco.line.coverage} + + + BRANCH + COVEREDRATIO + ${jacoco.branch.coverage} + + + + + + + + + + + + + chronicle-enterprise-snapshots diff --git a/pom.xml.versionsBackup b/pom.xml.versionsBackup deleted file mode 100644 index 009c57c6..00000000 --- a/pom.xml.versionsBackup +++ /dev/null @@ -1,281 +0,0 @@ - - - - - - 4.0.0 - - - net.openhft - java-parent-pom - 1.24.0 - - - chronicle-test-framework - 2.24ea7-SNAPSHOT - OpenHFT/Chronicle-Test-Framework - Chronicle-Test-Framework - jar - - - openhft - https://sonarcloud.io - - - - - - net.openhft - third-party-bom - pom - 3.24.0 - import - - - - - - - org.jetbrains - annotations - - - - org.slf4j - slf4j-simple - - - - io.github.classgraph - classgraph - - - - - org.junit.jupiter - junit-jupiter-api - test - - - - org.junit.jupiter - junit-jupiter-engine - test - - - - org.junit.jupiter - junit-jupiter-params - test - - - - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - - *.internal:*.internal.* - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - true - true - -Xlint:all - - - - - java8 - - compile - - - - java11 - - compile - - - ${project.basedir}/src/main/java11 - ${project.build.outputDirectory}/META-INF/versions/11 - - - - - - - org.apache.maven.plugins - maven-jar-plugin - - - - true - chronicle.test.framework - - - - - - - - org.apache.maven.plugins - maven-surefire-plugin - - - 4 - true - - - - - - org.jacoco - jacoco-maven-plugin - - - - prepare-agent - - - - report - prepare-package - - report - - - - - - - org.sonarsource.scanner.maven - sonar-maven-plugin - - - - - - - - Snapshot Repository - Snapshot Repository - https://oss.sonatype.org/content/repositories/snapshots - - true - always - - - false - never - - - - chronicle-enterprise-snapshots - Snapshot Repository - - https://nexus.chronicle.software/content/repositories/snapshots - - - true - - - - chronicle-enterprise-release - - https://nexus.chronicle.software/content/repositories/releases - - - true - - - - - - - java11 - - [11, - - - - - maven-resources-plugin - - - - src/main/resources/META-INF/services - - * - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - 11 - 11 - - **/jdk8/** - **/META-INF/services/** - - - - - - - - java17 - - [17, - - - - - org.apache.maven.plugins - maven-compiler-plugin - - 17 - 17 - - - - - - - - - scm:git:git@github.com:OpenHFT/Chronicle-Test-Framework.git - scm:git:git@github.com:OpenHFT/Chronicle-Test-Framework.git - scm:git:git@github.com:OpenHFT/Chronicle-Test-Framework.git - - master - - - diff --git a/src/main/adoc/project-requirements.adoc b/src/main/adoc/project-requirements.adoc deleted file mode 100644 index dfc67d74..00000000 --- a/src/main/adoc/project-requirements.adoc +++ /dev/null @@ -1,267 +0,0 @@ -= Requirements Document: Enhancing Chronicle Test Framework with AI Support -:toc: left -:lang: en-GB - -== 1. Introduction and Purpose == - -This document outlines the requirements for enhancing the Chronicle Test Framework (CTF). The primary goals are to improve its overall quality, ease of adoption, deepen its integration with the OpenHFT ecosystem, and strategically incorporate Artificial Intelligence (AI) to assist in its development and application. - -The envisioned framework aims to **complement existing testing frameworks** like JUnit and TestNG, rather than replacing them, by providing specialized capabilities, particularly for exhaustive test data generation and testing high-performance OpenHFT components. This document will serve as a guide for the development team in evolving CTF. - -The requirements are derived from: - -* An in-depth analysis titled "Enhancing Chronicle Test Framework: A Comparative Analysis and Roadmap for AI-Driven Improvements." -* A comparative analysis document titled "Chronicle Test Framework: Comparative Analysis and Improvement Opportunities." - -== 2. Current State of Chronicle Test Framework == - -The Chronicle Test Framework (CTF) is a Java library offering support classes tailored for crafting JUnit tests, compatible with both JUnit 4 and JUnit 5. Its core strength lies in generating a comprehensive range of test data combinations and permutations through utilities like `Permutation.of()`, `Combination.of()`, and `Product.of()`. These are primarily leveraged with JUnit 5's `@TestFactory` for dynamic test generation. - -Identified strengths include: - -* Specialized utilities for exhaustive test data generation. -* Clear, example-driven documentation for core features. -* Effective integration with JUnit 5's dynamic test generation. - -Identified limitations include: - -* Lack of formally documented overarching functional requirements or design principles. -* A feature set intensely focused on data generation, lacking broader testing utilities. -* No explicit extensibility model. -* Potential perception as a niche utility, limiting broader adoption. -* Reliance on basic JUnit assertions. - -These limitations, particularly the lack of formal design principles and a broader feature set, present challenges for leveraging AI effectively in the framework's evolution. - -== 3. High-Level Goals for Enhancement == - -The enhancement of CTF will be guided by the following high-level goals: - -* **HLG-1: Improve Overall Quality:** Enhance the robustness, feature set, and reliability of CTF. -* **HLG-2: Increase Ease of Adoption:** Make CTF more approachable, intuitive, and easier to integrate into development workflows. -* **HLG-3: Deepen OpenHFT Ecosystem Integration:** Provide specialized, high-value testing support for core OpenHFT libraries. -* **HLG-4: Enable AI-Driven Development and Usage:** Design CTF to facilitate AI-assisted test generation, refactoring, and analysis, as well as using AI to improve CTF itself. -* **HLG-5: Complement Existing Frameworks:** Ensure CTF continues to work alongside and enhance existing frameworks like JUnit and TestNG without aiming to replace their core functionalities. - -== 4. Functional Requirements == - -This section details the functional requirements for enhancing CTF. - -=== 4.1. Core Framework Enhancements === - -These requirements focus on modernizing CTF's core capabilities, drawing inspiration from established testing frameworks. - -REQ-FR-CORE-001: Expanded Annotation Support :: -**Description:** Introduce a suite of CTF-specific annotations for declarative configuration, lifecycle management, and metadata, tailored for OpenHFT contexts. -**Rationale:** Reduce boilerplate, improve test readability and maintainability. Provides clear markers for AI to understand test structure and dependencies. -**Examples:** `@ChronicleTestConfiguration`, `@DataSource`, specialized OpenHFT component lifecycle annotations. -**Reference:** Research Sec 3.1, User Doc "Opportunities for Improvement - JUnit 5 Integration Enhancements". - -REQ-FR-CORE-002: Enhanced Parameterized and Dynamic Test Capabilities :: -**Description:** Provide more integrated and intuitive mechanisms for parameter resolution and descriptive test naming for tests using CTF-generated data, akin to JUnit 5's `@ParameterizedTest` sources or TestNG's `@DataProvider`. -**Rationale:** Simplify the creation and management of parameterized tests leveraging CTF's data generation. -**Reference:** Research Sec 3.1. - -REQ-FR-CORE-003: Improved Assertion Capabilities :: -**Description:** Enhance assertion capabilities through one or a combination of: -* **Option A:** Deep, seamless integration with AssertJ. -* **Option B:** Development of a focused set of native fluent assertions within CTF, specifically for common OpenHFT data structures and testing scenarios. -**Rationale:** Improve test readability, maintainability, and developer productivity. Fluent assertions are preferred by developers. -**Reference:** Research Sec 3.1, User Doc "Fluent and Expressive Assertions", "Opportunities for Improvement - Leverage AssertJ". - -REQ-FR-CORE-004: Formal Extensibility Model :: -**Description:** Design and implement a formal extension model (e.g., inspired by JUnit 5's `@ExtendWith` or TestNG's listeners). -**Rationale:** Foster broader adoption, enable community contributions, allow tailoring to specific project needs, and provide clear integration points for AI-generated code or custom analysis. -**Possible Extensions:** Custom parameter resolvers, lifecycle callbacks, test instance post-processors, custom test execution behavior. -**Reference:** Research Sec 3.1. - -REQ-FR-CORE-005: TestNG Integration Support :: -**Description:** Provide official support and examples for using CTF data generation capabilities with TestNG's `@DataProvider` mechanism. -**Rationale:** Broaden CTF's user base beyond JUnit users and demonstrate its flexibility. -**Reference:** User Doc "Test Lifecycle and Configuration Ease", "Opportunities for Improvement - TestNG Support". - -=== 4.2. Developer Experience and Usability Improvements === - -These requirements aim to make CTF more user-friendly and easier to adopt. - -REQ-FR-UX-001: API Simplification for Common Scenarios :: -**Description:** Identify common usage patterns of CTF's generators and provide convenience APIs, builders, or pre-configured generators to encapsulate complexity. -**Rationale:** Lower the learning curve for new users and make CTF more readily applicable for simpler testing needs. -**Reference:** Research Sec 3.2. - -REQ-FR-UX-002: Comprehensive Documentation and Illustrative Examples :: -**Description:** Significantly expand documentation to include: -* "Getting Started" guide. -* "Best Practices" section. -* Complex, real-world examples (especially for OpenHFT components). -* Detailed explanations of new annotations, extensions, and APIs. -* Guidance on different testing types with CTF. -* Clear Javadoc for all public APIs. -* FAQ/Troubleshooting section. -**Rationale:** Paramount for adoption, effective use, and reducing the learning curve. -**Reference:** Research Sec 3.2, User Doc "Documentation Quality", "Opportunities for Improvement - Expanded Documentation". - -=== 4.3. Deepened Integration with OpenHFT Ecosystem === - -These requirements focus on providing specialized testing utilities for core OpenHFT libraries. - -REQ-FR-OHFT-001: Specialized Testing Utilities for Chronicle Queue :: -**Description:** Develop utilities such as `TestAppender`, `TestTailer`, assertion helpers for message content, and utilities for programmatic setup/teardown of test queues. Include helpers for testing specific queue behaviors. -**Rationale:** Simplify testing of Chronicle Queue, encapsulate common patterns, and reduce boilerplate. -**Reference:** Research Sec 3.3. -**Conceptual Example:** - -[source,java] ----- - @ExtendWith(ChronicleQueueExtension.class) - class MyQueueServiceTest { - @QueueResource(name = "inputQueue") - ChronicleQueue inputQueue; - - @Test - void testProcessMessage() { - ChronicleQueueTestUtil.writeMessage(inputQueue, writer -> writer.text("TestPayload")); - // ... service call ... - assertThatChronicleQueue(outputQueue) - .hasMessageCount(1) - .nextMessage(MyOutputMessage.class) - .field("status").isEqualTo("PROCESSED"); - } - } ----- - -REQ-FR-OHFT-002: Helpers for Chronicle Map Testing :: -**Description:** Introduce fixtures/extensions for creating/managing test `ChronicleMap` instances, rich assertions for map properties and values (especially `BytesMarshallable`), and utilities for verifying concurrent access or common interaction patterns (`getUsing`, `acquireUsing`). -**Rationale:** Simplify testing of Chronicle Map, provide powerful assertions for complex data types. -**Reference:** Research Sec 3.3. -**Conceptual Example:** - -[source,java] ----- - @ExtendWith(ChronicleMapExtension.class) - class MyMapCacheTest { - @MapResource(keyClass = String.class, valueClass = MyValue.class) - ChronicleMap cache; - - @Test - void testCachePutAndGet() { - MyValue valueToPut = new MyValue("data123", 42); - cache.put("itemKey1", valueToPut); - assertThatChronicleMap(cache) - .containsKey("itemKey1") - .valueForKey("itemKey1", MyValue.class) - .isEqualTo(valueToPut); - } - } ----- - -REQ-FR-OHFT-003: Support for Chronicle Wire Serialization Testing :: -**Description:** Create utilities for testing serialization/deserialization of `Marshallable` objects across different `WireTypes`, helpers for testing schema evolution, and assertions for inspecting raw `Bytes` content. -**Rationale:** Ensure data integrity and interoperability for components using Chronicle Wire. -**Reference:** Research Sec 3.3. - -REQ-FR-OHFT-004: Leveraging Chronicle Threads for Concurrency Testing :: -**Description:** Offer utilities for managing `EventLoop` instances in tests, helpers for testing `Pauser` strategies, and assertions/patterns for verifying thread safety and concurrent behavior. -**Rationale:** Simplify the setup and verification of complex concurrent test scenarios. -**Reference:** Research Sec 3.3. - -REQ-FR-OHFT-005: Multi-Process Test Support :: -**Description:** Provide documented, high-level utilities or patterns for creating and managing multi-process tests, including lifecycle management and output capture. -**Rationale:** Facilitate testing of inter-process communication scenarios common with OpenHFT libraries. -**Reference:** User Doc "Suitability for Low-Latency and Performance-Critical Tests". - -REQ-FR-OHFT-006: Resource and Concurrency Test Utilities :: -**Description:** Introduce utilities to aid in testing resource management and concurrency aspects critical for OpenHFT libraries: -* **Thread Leak Detection:** A utility or extension to check for orphaned threads after test execution. -* **No-GC/Low-Allocation Assertions:** Utilities or guidance for asserting minimal or no garbage collection/object allocation during specific operations. -* **Temporary File Management:** Robust utilities for creating and cleaning up temporary files/directories used by tests, complementing JUnit 5's `@TempDir` or providing alternatives for other contexts. -**Rationale:** Address common pain points in testing high-performance, resource-intensive applications and ensure test reliability. -**Reference:** User Doc "Opportunities for Improvement - Resource and Concurrency Utilities". - -=== 4.4. AI-Specific Functional Requirements === - -These requirements are specifically aimed at designing CTF to effectively leverage and support AI tools. - -REQ-FR-AI-001: AI-Identifiable Refactoring Opportunities :: -**Description:** CTF's new utilities, annotations, and extensions (as defined in REQ-FR-CORE and REQ-FR-OHFT) must be designed to serve as clear, beneficial, higher-level abstractions that AI tools can refactor existing tests to use. -**Rationale:** Enable AI to automate the modernization and simplification of test suites using CTF features. -**Reference:** Research Sec 3.4. - -REQ-FR-AI-002: Patterns for AI-Assisted Test Generation :: -**Description:** CTF shall provide and document patterns for how AI can: -* Use CTF's data generators (`Permutation`, `Combination`, `Product`) for method parameters or message payloads in AI-generated tests. -* Leverage CTF's OpenHFT-specific testing utilities and assertions to generate meaningful integration tests. -* Be guided to suggest new test cases for uncovered configurations, edge conditions, or interaction scenarios based on analysis of existing CTF tests and OpenHFT component configurations. -**Rationale:** Combine CTF's structured generation and OpenHFT-specific knowledge with AI's code generation and analysis capabilities. -**Reference:** Research Sec 3.4. - -REQ-FR-AI-003: AI-Consumable API Design :: -**Description:** All new CTF APIs (annotations, extension points, fluent builders, assertion methods) must be designed with AI consumption in mind, ensuring they are: -* **Clearly Defined and Strongly Typed:** Minimize ambiguity, have clear contracts. -* **Discoverable:** Comprehensive Javadoc, consistent naming, logical structure. -* **Composable:** Designed as smaller, composable units of functionality. -**Rationale:** Facilitate correct and effective use of CTF features by AI tools. Good API design for humans is critical for AI. -**Reference:** Research Sec 3.4. - -REQ-FR-AI-004: Semantic Information Conveyance for AI :: -**Description:** CTF APIs, especially annotations, should be designed to carry semantic information about the test's intent or the configuration of the component under test, beyond syntactic structure. -**Rationale:** Enable AI to make more intelligent suggestions, generate more effective tests, and understand the domain-specific (OpenHFT) context. -**Example:** An annotation like `@ChronicleQueueTest(role="messageConsumer", expectedThroughput="100k_msgs_sec")` offers richer context than a simple `@Test`. -**Reference:** Research Sec 3.4. - -== 5. Non-Functional Requirements == - -This section details the non-functional requirements for CTF. - -REQ-NFR-PERF-001: Performance of CTF :: -**Description:** CTF's data generation and utility functions should be performant and not add undue overhead to test execution. Guidance should be provided on managing test suite execution time when using exhaustive generation. -**Rationale:** Ensure CTF itself does not become a bottleneck, especially in performance-sensitive OpenHFT testing environments. -**Reference:** User Doc "Suitability for Low-Latency and Performance-Critical Tests". - -*REQ-NFR-USAB-001: Usability and Learnability :: -**Description:** The API should be intuitive, consistent, and easy to learn, particularly for developers familiar with Java and standard testing frameworks. The learning curve should be minimized. -**Rationale:** Critical for adoption and effective use. -**Reference:** User Doc "API Consistency and Learning Curve". - -REQ-NFR-MAIN-001: Maintainability :: -**Description:** CTF's codebase should be well-structured, documented, and testable to ensure ease of maintenance and future enhancements. Design principles like SRP and ISP should be considered. -**Rationale:** Long-term health and evolution of the framework. -**Reference:** Research Sec 3.4. - -REQ-NFR-COMP-001: Compatibility :: -**Description:** CTF must maintain compatibility with JUnit 4 and JUnit 5. Integration with TestNG should be supported as a complementary approach. Compatibility with relevant Java versions must be clearly stated. -**Rationale:** Support existing user bases and allow flexibility in framework choice. -**Reference:** CTF Current State, User Doc "Test Lifecycle and Configuration Ease". - -REQ-NFR-DOC-001: High-Quality Documentation :: -**Description:** All features, APIs, extension points, and usage patterns must be thoroughly documented with clear examples. (Covered in REQ-FR-UX-002 but emphasized here as an NFR). -**Rationale:** Essential for user adoption, understanding, and effective utilization. - -REQ-NFR-STAB-001: Stability and Versioning :: -**Description:** CTF should move towards stable versioning, with clear communication about the stability level of releases (e.g., milestones, RCs, GA). Compatibility between CTF versions and OpenHFT library versions should be indicated. -**Rationale:** Improve developer confidence and simplify dependency management. -**Reference:** User Doc "Opportunities for Improvement - Standardization with Testing Conventions". - -== 6. AI-Driven Development and Application Strategy == - -The enhancement of CTF will proactively incorporate AI in two main ways: - -=== 6.1. AI in the Development *of* CTF === -AI-DEV-CTF-001: Suggesting Refinements :: AI tools may be used to analyze the existing CTF codebase and the proposed APIs to suggest refinements, identify potential inconsistencies, or propose alternative designs based on common Java and testing framework patterns. -I-DEV-CTF-002: Boilerplate Code Generation :: For implementing well-defined, repetitive structures within CTF (e.g., new assertion methods following a pattern, boilerplate for new OpenHFT utility classes), AI can assist in generating initial code. -AI-DEV-CTF-003: Documentation Assistance:** AI tools can help in generating Javadoc stubs, summarizing features for release notes, or even drafting initial sections of user guides based on code and requirements. - -=== 6.2. AI in the Application *by Users of* CTF === -This is primarily covered by REQ-FR-AI-* requirements. The strategy is to make CTF "AI-friendly" so that: - -AI-APP-CTF-001: AI can understand CTF test structures :: Semantic annotations and clear APIs will enable AI to better parse and understand tests written with CTF. -AI-APP-CTF-002: AI can leverage CTF features for test generation :: AI tools can be guided to use CTF's data generators and OpenHFT-specific utilities to create more comprehensive and relevant tests. -* _Example:_ An AI agent, using `ChronicleQueueTestUtil` and `Permutation.of()`, could generate tests that try every ordering of different message types written to a queue, verifying that each ordering is processed correctly. -I-APP-CTF-003: AI can assist in refactoring tests to use CTF :: As CTF evolves with more powerful utilities, AI can help migrate older tests (or tests not using CTF optimally) to leverage these new features, improving test quality and conciseness. -AI-APP-CTF-004: AI can perform advanced test analysis :: With richer semantic information from CTF tests, AI might be able to perform more sophisticated analyses, such as identifying redundant tests, suggesting coverage improvements for specific OpenHFT behaviors, or even predicting flaky tests based on patterns. - -By embedding AI considerations into the design of CTF, the framework will not only be improved by AI but will also empower its users to leverage AI for more effective testing within the OpenHFT ecosystem. - -== 7. Conclusion == - -The Chronicle Test Framework has a solid foundation in test data generation. By implementing the requirements outlined in this document, CTF will evolve into a more comprehensive, user-friendly, and powerful testing tool, especially for the OpenHFT ecosystem. The strategic incorporation of AI-support features will position CTF as a forward-looking framework, enabling new levels of efficiency and intelligence in software testing. The key is to build upon its unique strengths while addressing current limitations and embracing modern testing paradigms, all while ensuring it remains a valuable complement to mainstream testing frameworks. \ No newline at end of file diff --git a/src/main/config/checkstyle.xml b/src/main/config/checkstyle.xml new file mode 100644 index 00000000..844dd904 --- /dev/null +++ b/src/main/config/checkstyle.xml @@ -0,0 +1,210 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/config/pmd-exclude.properties b/src/main/config/pmd-exclude.properties new file mode 100644 index 00000000..4cfd8b36 --- /dev/null +++ b/src/main/config/pmd-exclude.properties @@ -0,0 +1,2 @@ +# Chronicle-Test-Framework PMD exclusions. +# Use fully qualified paths (e.g. net/openhft/...)=RuleName and include JUSTIFICATION in review notes. diff --git a/src/main/config/spotbugs-exclude.xml b/src/main/config/spotbugs-exclude.xml new file mode 100644 index 00000000..2235a8a4 --- /dev/null +++ b/src/main/config/spotbugs-exclude.xml @@ -0,0 +1,69 @@ + + + + + + + + + Ephemeral sockets are used exclusively in test setups. + + + + + + + Optional GC between flaky test iterations is intentional. + + + + + + + Defensive programming to fail fast when configuration is invalid. + + + + + + + ArchUnit supplies managed classpath entries; URIs are not user controlled. + + + + + + Tracker mutates caller-supplied map to report results. + + + + + + + ProxyConnection must operate on provided SocketChannel instance. + + + + + + + ProcessBuilder invoked with pre-split argument list; no shell interpretation occurs. + + + + + + + Test utilities deliberately request GC for determinism. + + + + + + Test utilities deliberately request GC for determinism. + + + diff --git a/src/main/docs/project-requirements.adoc b/src/main/docs/project-requirements.adoc new file mode 100644 index 00000000..76521d16 --- /dev/null +++ b/src/main/docs/project-requirements.adoc @@ -0,0 +1,287 @@ += Requirements Document: Enhancing Chronicle Test Framework with AI Support +:toc: left +:lang: en-GB + +== 1. Introduction and Purpose == + +This document outlines the requirements for enhancing the Chronicle Test Framework (CTF). +The primary goals are to improve its overall quality, ease of adoption, deepen its integration with the OpenHFT ecosystem, and strategically incorporate Artificial Intelligence (AI) to assist in its development and application. + +The envisioned framework aims to *complement existing testing frameworks* like JUnit and TestNG, rather than replacing them, by providing specialized capabilities, particularly for exhaustive test data generation and testing high-performance OpenHFT components. +This document will serve as a guide for the development team in evolving CTF. + +The requirements are derived from: + +* An in-depth analysis titled "Enhancing Chronicle Test Framework: A Comparative Analysis and Roadmap for AI-Driven Improvements." +* A comparative analysis document titled "Chronicle Test Framework: Comparative Analysis and Improvement Opportunities." + +== 2. Current State of Chronicle Test Framework == + +The Chronicle Test Framework (CTF) is a Java library offering support classes tailored for crafting JUnit tests, compatible with both JUnit 4 and JUnit 5. Its core strength lies in generating a comprehensive range of test data combinations and permutations through utilities like `Permutation.of()`, `Combination.of()`, and `Product.of()`. +These are primarily leveraged with JUnit 5's `@TestFactory` for dynamic test generation. + +Identified strengths include: + +* Specialized utilities for exhaustive test data generation. +* Clear, example-driven documentation for core features. +* Effective integration with JUnit 5's dynamic test generation. + +Identified limitations include: + +* Lack of formally documented overarching functional requirements or design principles. +* A feature set intensely focused on data generation, lacking broader testing utilities. +* No explicit extensibility model. +* Potential perception as a niche utility, limiting broader adoption. +* Reliance on basic JUnit assertions. + +These limitations, particularly the lack of formal design principles and a broader feature set, present challenges for leveraging AI effectively in the framework's evolution. + +== 3. High-Level Goals for Enhancement == + +The enhancement of CTF will be guided by the following high-level goals: + +HLG-1: Improve Overall Quality :: +Enhance the robustness, feature set, and reliability of CTF. +HLG-2: Increase Ease of Adoption :: +Make CTF more approachable, intuitive, and easier to integrate into development workflows. +HLG-3: Deepen OpenHFT Ecosystem Integration :: +Provide specialized, high-value testing support for core OpenHFT libraries. +HLG-4: Enable AI-Driven Development and Usage :: +Design CTF to facilitate AI-assisted test generation, refactoring, and analysis, as well as using AI to improve CTF itself. +HLG-5: Complement Existing Frameworks :: +Ensure CTF continues to work alongside and enhance existing frameworks like JUnit and TestNG without aiming to replace their core functionalities. + +== 4. Functional Requirements == + +This section details the functional requirements for enhancing CTF. + +=== 4.1. Core Framework Enhancements === + +These requirements focus on modernizing CTF's core capabilities, drawing inspiration from established testing frameworks. + +REQ-FR-CORE-001: Expanded Annotation Support :: +*Description:* Introduce a suite of CTF-specific annotations for declarative configuration, lifecycle management, and metadata, tailored for OpenHFT contexts. +*Rationale:* Reduce boilerplate, improve test readability and maintainability. Provides clear markers for AI to understand test structure and dependencies. +*Examples:* `@ChronicleTestConfiguration`, `@DataSource`, specialized OpenHFT component lifecycle annotations. +*Reference:* Research Sec 3.1, User Doc "Opportunities for Improvement - JUnit 5 Integration Enhancements". + +REQ-FR-CORE-002: Enhanced Parameterized and Dynamic Test Capabilities :: +*Description:* Provide more integrated and intuitive mechanisms for parameter resolution and descriptive test naming for tests using CTF-generated data, akin to JUnit 5's `@ParameterizedTest` sources or TestNG's `@DataProvider`. +*Rationale:* Simplify the creation and management of parameterized tests leveraging CTF's data generation. +*Reference:* Research Sec 3.1. + +REQ-FR-CORE-003: Improved Assertion Capabilities :: +*Description:* Enhance assertion capabilities through one or a combination of: +Option A :: +Deep, seamless integration with AssertJ. +Option B :: +Development of a focused set of native fluent assertions within CTF, specifically for common OpenHFT data structures and testing scenarios. +*Rationale:* Improve test readability, maintainability, and developer productivity. Fluent assertions are preferred by developers. +*Reference:* Research Sec 3.1, User Doc "Fluent and Expressive Assertions", "Opportunities for Improvement - Leverage AssertJ". + +REQ-FR-CORE-004: Formal Extensibility Model :: +*Description:* Design and implement a formal extension model (e.g., inspired by JUnit 5's `@ExtendWith` or TestNG's listeners). +*Rationale:* Foster broader adoption, enable community contributions, allow tailoring to specific project needs, and provide clear integration points for AI-generated code or custom analysis. +*Possible Extensions:* Custom parameter resolvers, lifecycle callbacks, test instance post-processors, custom test execution behavior. +*Reference:* Research Sec 3.1. + +REQ-FR-CORE-005: TestNG Integration Support :: +*Description:* Provide official support and examples for using CTF data generation capabilities with TestNG's `@DataProvider` mechanism. +*Rationale:* Broaden CTF's user base beyond JUnit users and demonstrate its flexibility. +*Reference:* User Doc "Test Lifecycle and Configuration Ease", "Opportunities for Improvement - TestNG Support". + +=== 4.2. Developer Experience and Usability Improvements === + +These requirements aim to make CTF more user-friendly and easier to adopt. + +REQ-FR-UX-001: API Simplification for Common Scenarios :: +*Description:* Identify common usage patterns of CTF's generators and provide convenience APIs, builders, or pre-configured generators to encapsulate complexity. +*Rationale:* Lower the learning curve for new users and make CTF more readily applicable for simpler testing needs. +*Reference:* Research Sec 3.2. + +REQ-FR-UX-002: Comprehensive Documentation and Illustrative Examples :: +*Description:* Significantly expand documentation to include: +* "Getting Started" guide. +* "Best Practices" section. +* Complex, real-world examples (especially for OpenHFT components). +* Detailed explanations of new annotations, extensions, and APIs. +* Guidance on different testing types with CTF. +* Clear Javadoc for all public APIs. +* FAQ/Troubleshooting section. +*Rationale:* Paramount for adoption, effective use, and reducing the learning curve. +*Reference:* Research Sec 3.2, User Doc "Documentation Quality", "Opportunities for Improvement - Expanded Documentation". + +=== 4.3. Deepened Integration with OpenHFT Ecosystem === + +These requirements focus on providing specialized testing utilities for core OpenHFT libraries. + +REQ-FR-OHFT-001: Specialized Testing Utilities for Chronicle Queue :: +*Description:* Develop utilities such as `TestAppender`, `TestTailer`, assertion helpers for message content, and utilities for programmatic setup/teardown of test queues. Include helpers for testing specific queue behaviors. +*Rationale:* Simplify testing of Chronicle Queue, encapsulate common patterns, and reduce boilerplate. +*Reference:* Research Sec 3.3. +*Conceptual Example:* + +[source,java] +---- + @ExtendWith(ChronicleQueueExtension.class) + class MyQueueServiceTest { + @QueueResource(name = "inputQueue") + ChronicleQueue inputQueue; + + @Test + void testProcessMessage() { + ChronicleQueueTestUtil.writeMessage(inputQueue, writer -> writer.text("TestPayload")); + // ... service call ... + assertThatChronicleQueue(outputQueue) + .hasMessageCount(1) + .nextMessage(MyOutputMessage.class) + .field("status").isEqualTo("PROCESSED"); + } + } +---- + +REQ-FR-OHFT-002: Helpers for Chronicle Map Testing :: +*Description:* Introduce fixtures/extensions for creating/managing test `ChronicleMap` instances, rich assertions for map properties and values (especially `BytesMarshallable`), and utilities for verifying concurrent access or common interaction patterns (`getUsing`, `acquireUsing`). +*Rationale:* Simplify testing of Chronicle Map, provide powerful assertions for complex data types. +*Reference:* Research Sec 3.3. +*Conceptual Example:* + +[source,java] +---- + @ExtendWith(ChronicleMapExtension.class) + class MyMapCacheTest { + @MapResource(keyClass = String.class, valueClass = MyValue.class) + ChronicleMap cache; + + @Test + void testCachePutAndGet() { + MyValue valueToPut = new MyValue("data123", 42); + cache.put("itemKey1", valueToPut); + assertThatChronicleMap(cache) + .containsKey("itemKey1") + .valueForKey("itemKey1", MyValue.class) + .isEqualTo(valueToPut); + } + } +---- + +REQ-FR-OHFT-003: Support for Chronicle Wire Serialization Testing :: +*Description:* Create utilities for testing serialization/deserialization of `Marshallable` objects across different `WireTypes`, helpers for testing schema evolution, and assertions for inspecting raw `Bytes` content. +*Rationale:* Ensure data integrity and interoperability for components using Chronicle Wire. +*Reference:* Research Sec 3.3. + +REQ-FR-OHFT-004: Leveraging Chronicle Threads for Concurrency Testing :: +*Description:* Offer utilities for managing `EventLoop` instances in tests, helpers for testing `Pauser` strategies, and assertions/patterns for verifying thread safety and concurrent behavior. +*Rationale:* Simplify the setup and verification of complex concurrent test scenarios. +*Reference:* Research Sec 3.3. + +REQ-FR-OHFT-005: Multi-Process Test Support :: +*Description:* Provide documented, high-level utilities or patterns for creating and managing multi-process tests, including lifecycle management and output capture. +*Rationale:* Facilitate testing of inter-process communication scenarios common with OpenHFT libraries. +*Reference:* User Doc "Suitability for Low-Latency and Performance-Critical Tests". + +REQ-FR-OHFT-006: Resource and Concurrency Test Utilities :: +*Description:* Introduce utilities to aid in testing resource management and concurrency aspects critical for OpenHFT libraries: +Thread Leak Detection :: +A utility or extension to check for orphaned threads after test execution. +No-GC/Low-Allocation Assertions :: +Utilities or guidance for asserting minimal or no garbage collection/object allocation during specific operations. +Temporary File Management :: +Robust utilities for creating and cleaning up temporary files/directories used by tests, complementing JUnit 5's `@TempDir` or providing alternatives for other contexts. +*Rationale:* Address common pain points in testing high-performance, resource-intensive applications and ensure test reliability. +*Reference:* User Doc "Opportunities for Improvement - Resource and Concurrency Utilities". + +=== 4.4. AI-Specific Functional Requirements === + +These requirements are specifically aimed at designing CTF to effectively leverage and support AI tools. + +REQ-FR-AI-001: AI-Identifiable Refactoring Opportunities :: +*Description:* CTF's new utilities, annotations, and extensions (as defined in REQ-FR-CORE and REQ-FR-OHFT) must be designed to serve as clear, beneficial, higher-level abstractions that AI tools can refactor existing tests to use. +*Rationale:* Enable AI to automate the modernization and simplification of test suites using CTF features. +*Reference:* Research Sec 3.4. + +REQ-FR-AI-002: Patterns for AI-Assisted Test Generation :: +*Description:* CTF shall provide and document patterns for how AI can: +* Use CTF's data generators (`Permutation`, `Combination`, `Product`) for method parameters or message payloads in AI-generated tests. +* Leverage CTF's OpenHFT-specific testing utilities and assertions to generate meaningful integration tests. +* Be guided to suggest new test cases for uncovered configurations, edge conditions, or interaction scenarios based on analysis of existing CTF tests and OpenHFT component configurations. +*Rationale:* Combine CTF's structured generation and OpenHFT-specific knowledge with AI's code generation and analysis capabilities. +*Reference:* Research Sec 3.4. + +REQ-FR-AI-003: AI-Consumable API Design :: +*Description:* All new CTF APIs (annotations, extension points, fluent builders, assertion methods) must be designed with AI consumption in mind, ensuring they are: +Clearly Defined and Strongly Typed :: +Minimize ambiguity, have clear contracts. +Discoverable :: +Comprehensive Javadoc, consistent naming, logical structure. +Composable :: +Designed as smaller, composable units of functionality. +*Rationale:* Facilitate correct and effective use of CTF features by AI tools. Good API design for humans is critical for AI. +*Reference:* Research Sec 3.4. + +REQ-FR-AI-004: Semantic Information Conveyance for AI :: +*Description:* CTF APIs, especially annotations, should be designed to carry semantic information about the test's intent or the configuration of the component under test, beyond syntactic structure. +*Rationale:* Enable AI to make more intelligent suggestions, generate more effective tests, and understand the domain-specific (OpenHFT) context. +*Example:* An annotation like `@ChronicleQueueTest(role="messageConsumer", expectedThroughput="100k_msgs_sec")` offers richer context than a simple `@Test`. +*Reference:* Research Sec 3.4. + +== 5. Non-Functional Requirements == + +This section details the non-functional requirements for CTF. + +REQ-NFR-PERF-001: Performance of CTF :: +*Description:* CTF's data generation and utility functions should be performant and not add undue overhead to test execution. Guidance should be provided on managing test suite execution time when using exhaustive generation. +*Rationale:* Ensure CTF itself does not become a bottleneck, especially in performance-sensitive OpenHFT testing environments. +*Reference:* User Doc "Suitability for Low-Latency and Performance-Critical Tests". + +*REQ-NFR-USAB-001: Usability and Learnability :: +*Description:* The API should be intuitive, consistent, and easy to learn, particularly for developers familiar with Java and standard testing frameworks. The learning curve should be minimized. +*Rationale:* Critical for adoption and effective use. +*Reference:* User Doc "API Consistency and Learning Curve". + +REQ-NFR-MAIN-001: Maintainability :: +*Description:* CTF's codebase should be well-structured, documented, and testable to ensure ease of maintenance and future enhancements. Design principles like SRP and ISP should be considered. +*Rationale:* Long-term health and evolution of the framework. +*Reference:* Research Sec 3.4. + +REQ-NFR-COMP-001: Compatibility :: +*Description:* CTF must maintain compatibility with JUnit 4 and JUnit 5. Integration with TestNG should be supported as a complementary approach. Compatibility with relevant Java versions must be clearly stated. +*Rationale:* Support existing user bases and allow flexibility in framework choice. +*Reference:* CTF Current State, User Doc "Test Lifecycle and Configuration Ease". + +REQ-NFR-DOC-001: High-Quality Documentation :: +*Description:* All features, APIs, extension points, and usage patterns must be thoroughly documented with clear examples. (Covered in REQ-FR-UX-002 but emphasized here as an NFR). +*Rationale:* Essential for user adoption, understanding, and effective utilization. + +REQ-NFR-STAB-001: Stability and Versioning :: +*Description:* CTF should move towards stable versioning, with clear communication about the stability level of releases (e.g., milestones, RCs, GA). Compatibility between CTF versions and OpenHFT library versions should be indicated. +*Rationale:* Improve developer confidence and simplify dependency management. +*Reference:* User Doc "Opportunities for Improvement - Standardization with Testing Conventions". + +== 6. AI-Driven Development and Application Strategy == + +The enhancement of CTF will proactively incorporate AI in two main ways: + +=== 6.1. AI in the Development _of_ CTF === +AI-DEV-CTF-001: Suggesting Refinements :: AI tools may be used to analyze the existing CTF codebase and the proposed APIs to suggest refinements, identify potential inconsistencies, or propose alternative designs based on common Java and testing framework patterns. +I-DEV-CTF-002: Boilerplate Code Generation :: For implementing well-defined, repetitive structures within CTF (e.g., new assertion methods following a pattern, boilerplate for new OpenHFT utility classes), AI can assist in generating initial code. +AI-DEV-CTF-003: Documentation Assistance:** AI tools can help in generating Javadoc stubs, summarizing features for release notes, or even drafting initial sections of user guides based on code and requirements. + +=== 6.2. AI in the Application _by Users of_ CTF === +This is primarily covered by REQ-FR-AI-* requirements. +The strategy is to make CTF "AI-friendly" so that: + +AI-APP-CTF-001: AI can understand CTF test structures :: Semantic annotations and clear APIs will enable AI to better parse and understand tests written with CTF. +AI-APP-CTF-002: AI can leverage CTF features for test generation :: AI tools can be guided to use CTF's data generators and OpenHFT-specific utilities to create more comprehensive and relevant tests. +* _Example:_ An AI agent, using `ChronicleQueueTestUtil` and `Permutation.of()`, could generate tests that try every ordering of different message types written to a queue, verifying that each ordering is processed correctly. +I-APP-CTF-003: AI can assist in refactoring tests to use CTF :: As CTF evolves with more powerful utilities, AI can help migrate older tests (or tests not using CTF optimally) to leverage these new features, improving test quality and conciseness. +AI-APP-CTF-004: AI can perform advanced test analysis :: With richer semantic information from CTF tests, AI might be able to perform more sophisticated analyses, such as identifying redundant tests, suggesting coverage improvements for specific OpenHFT behaviors, or even predicting flaky tests based on patterns. + +By embedding AI considerations into the design of CTF, the framework will not only be improved by AI but will also empower its users to leverage AI for more effective testing within the OpenHFT ecosystem. + +== 7. Conclusion == + +The Chronicle Test Framework has a solid foundation in test data generation. +By implementing the requirements outlined in this document, CTF will evolve into a more comprehensive, user-friendly, and powerful testing tool, especially for the OpenHFT ecosystem. +The strategic incorporation of AI-support features will position CTF as a forward-looking framework, enabling new levels of efficiency and intelligence in software testing. +The key is to build upon its unique strengths while addressing current limitations and embracing modern testing paradigms, all while ensuring it remains a valuable complement to mainstream testing frameworks. diff --git a/src/main/java/net/openhft/chronicle/testframework/Combination.java b/src/main/java/net/openhft/chronicle/testframework/Combination.java index ccf93a2c..d07515fe 100644 --- a/src/main/java/net/openhft/chronicle/testframework/Combination.java +++ b/src/main/java/net/openhft/chronicle/testframework/Combination.java @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2006-2020, Speedment, Inc. All Rights Reserved. + * Copyright 2006-2025 chronicle.software * * Licensed 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 diff --git a/src/main/java/net/openhft/chronicle/testframework/GcControls.java b/src/main/java/net/openhft/chronicle/testframework/GcControls.java index c5024ef5..c237597c 100644 --- a/src/main/java/net/openhft/chronicle/testframework/GcControls.java +++ b/src/main/java/net/openhft/chronicle/testframework/GcControls.java @@ -1,7 +1,5 @@ /* - * Copyright 2016-2022 chronicle.software - * - * https://chronicle.software + * Copyright 2016-2025 chronicle.software * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/net/openhft/chronicle/testframework/NetworkUtil.java b/src/main/java/net/openhft/chronicle/testframework/NetworkUtil.java index c7d40014..3128c828 100644 --- a/src/main/java/net/openhft/chronicle/testframework/NetworkUtil.java +++ b/src/main/java/net/openhft/chronicle/testframework/NetworkUtil.java @@ -1,5 +1,4 @@ package net.openhft.chronicle.testframework; - import java.io.IOException; import java.net.ServerSocket; @@ -27,7 +26,7 @@ public enum NetworkUtil { public static int getAvailablePort() { // Binds a temporary socket to obtain an ephemeral port. // The port is released when the socket closes. - try (final ServerSocket serverSocket = new ServerSocket(0)) { + try (ServerSocket serverSocket = new ServerSocket(0)) { return serverSocket.getLocalPort(); } catch (IOException e) { throw new RuntimeException("Failed to find an available port", e); diff --git a/src/main/java/net/openhft/chronicle/testframework/Permutation.java b/src/main/java/net/openhft/chronicle/testframework/Permutation.java index 9e6ddb67..d0e7256d 100644 --- a/src/main/java/net/openhft/chronicle/testframework/Permutation.java +++ b/src/main/java/net/openhft/chronicle/testframework/Permutation.java @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2006-2020, Speedment, Inc. All Rights Reserved. + * Copyright 2006-2025 chronicle.software * * Licensed 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 diff --git a/src/main/java/net/openhft/chronicle/testframework/internal/CombinationUtil.java b/src/main/java/net/openhft/chronicle/testframework/internal/CombinationUtil.java index 8dc7728c..521c8e1d 100644 --- a/src/main/java/net/openhft/chronicle/testframework/internal/CombinationUtil.java +++ b/src/main/java/net/openhft/chronicle/testframework/internal/CombinationUtil.java @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2006-2020, Speedment, Inc. All Rights Reserved. + * Copyright 2006-2025 chronicle.software * * Licensed 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 @@ -53,7 +53,7 @@ public static Stream> of(final T... items) { /** * Convenience wrapper that generates combinations for a collection. */ - @SuppressWarnings("unchecked") + @SuppressWarnings({"unchecked", "PMD.ClassCastExceptionWithToArray"}) public static Stream> of(final Collection items) { return of((T[]) items.toArray()); } @@ -61,7 +61,7 @@ public static Stream> of(final Collection items) { /** * Convenience wrapper that generates combinations for a stream. */ - @SuppressWarnings("unchecked") + @SuppressWarnings({"unchecked", "PMD.ClassCastExceptionWithToArray"}) public static Stream> of(final Stream items) { return of((T[]) items.toArray()); } diff --git a/src/main/java/net/openhft/chronicle/testframework/internal/PermutationUtil.java b/src/main/java/net/openhft/chronicle/testframework/internal/PermutationUtil.java index 803ca724..4079667c 100644 --- a/src/main/java/net/openhft/chronicle/testframework/internal/PermutationUtil.java +++ b/src/main/java/net/openhft/chronicle/testframework/internal/PermutationUtil.java @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2006-2020, Speedment, Inc. All Rights Reserved. + * Copyright 2006-2025 chronicle.software * * Licensed 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 diff --git a/src/main/java/net/openhft/chronicle/testframework/internal/ProductUtil.java b/src/main/java/net/openhft/chronicle/testframework/internal/ProductUtil.java index ef33c4b6..aa411065 100644 --- a/src/main/java/net/openhft/chronicle/testframework/internal/ProductUtil.java +++ b/src/main/java/net/openhft/chronicle/testframework/internal/ProductUtil.java @@ -74,8 +74,8 @@ public static Stream of(Collection ts, requireNonNull(constructor); return ts.stream() .flatMap(t -> us.stream() - .flatMap((u -> vs.stream() - .map(v -> constructor.apply(t, u, v))))); + .flatMap(u -> vs.stream() + .map(v -> constructor.apply(t, u, v)))); } /** @@ -96,8 +96,8 @@ public static Stream of(Stream ts, final List innerV = vs.collect(Collectors.toList()); return ts .flatMap(t -> innerU.stream() - .flatMap((u -> innerV.stream() - .map(v -> constructor.apply(t, u, v))))); + .flatMap(u -> innerV.stream() + .map(v -> constructor.apply(t, u, v)))); } /** diff --git a/src/main/java/net/openhft/chronicle/testframework/internal/SeriesUtil.java b/src/main/java/net/openhft/chronicle/testframework/internal/SeriesUtil.java index 6cdc9254..9e57391b 100644 --- a/src/main/java/net/openhft/chronicle/testframework/internal/SeriesUtil.java +++ b/src/main/java/net/openhft/chronicle/testframework/internal/SeriesUtil.java @@ -45,7 +45,7 @@ public static LongStream primes() { } private static boolean isPrime(long number) { - return LongStream.rangeClosed(2, (int) (Math.sqrt(number))) + return LongStream.rangeClosed(2, (int) Math.sqrt(number)) .allMatch(n -> number % n != 0); } diff --git a/src/main/java/net/openhft/chronicle/testframework/internal/VanillaExceptionTracker.java b/src/main/java/net/openhft/chronicle/testframework/internal/VanillaExceptionTracker.java index d6aaef80..29943560 100644 --- a/src/main/java/net/openhft/chronicle/testframework/internal/VanillaExceptionTracker.java +++ b/src/main/java/net/openhft/chronicle/testframework/internal/VanillaExceptionTracker.java @@ -106,7 +106,7 @@ public void checkExceptions() { } for (Map.Entry, String> ignoredException : ignoredExceptions.entrySet()) { if (exceptions.keySet().removeIf(ignoredException.getKey())) - LOGGER.debug("Ignored {}", ignoredException.getValue()); + LOGGER.debug("Ignored {}", sanitize(ignoredException.getValue())); } if (hasExceptions()) { @@ -188,7 +188,7 @@ private boolean hasExceptions() { private void dumpException() { for (@NotNull Map.Entry entry : exceptions.entrySet()) { final T key = entry.getKey(); - LOGGER.warn(exceptionRenderer.apply(key), throwableExtractor.apply(key)); + LOGGER.warn(sanitize(exceptionRenderer.apply(key)), throwableExtractor.apply(key)); final Integer value = entry.getValue(); if (value > 1) LOGGER.warn("Repeated {} times", value); @@ -204,4 +204,11 @@ private void checkFinalised() { throw new IllegalStateException("VanillaExceptionTracker is single use, you create it, add expectations/ignores, run tests, call check and then dispose of it."); } } + + private static String sanitize(String value) { + if (value == null) { + return null; + } + return value.replace('\r', ' ').replace('\n', ' '); + } } diff --git a/src/main/java/net/openhft/chronicle/testframework/internal/VanillaFlakyTestRunner.java b/src/main/java/net/openhft/chronicle/testframework/internal/VanillaFlakyTestRunner.java index 839c1592..995b51e9 100644 --- a/src/main/java/net/openhft/chronicle/testframework/internal/VanillaFlakyTestRunner.java +++ b/src/main/java/net/openhft/chronicle/testframework/internal/VanillaFlakyTestRunner.java @@ -1,7 +1,6 @@ package net.openhft.chronicle.testframework.internal; import net.openhft.chronicle.testframework.FlakyTestRunner; - import java.util.concurrent.atomic.AtomicBoolean; import static java.util.Objects.requireNonNull; @@ -40,7 +39,8 @@ public void run() throws X { if (!inRun.compareAndSet(false, true)) throw new AssertionError("Can't run nested"); try { - for (int i = 0; i < builder.maxIterations; i++) { + final int maxIterations = builder.flakyOnThisArchitecture ? builder.maxIterations : Math.max(1, builder.maxIterations); + for (int i = 0; i < maxIterations; i++) { try { builder.action.run(); if (i > 0) { @@ -49,7 +49,7 @@ public void run() throws X { break; } catch (Throwable x) { // Using Throwable above allows AssertionError to be retried - if (i == (builder.maxIterations - 1)) { + if (i == (maxIterations - 1)) { throw x; } builder.errorLogger.accept("Rerunning failing test run " + (i + 2)); diff --git a/src/main/java/net/openhft/chronicle/testframework/internal/apimetrics/StandardAccumulator1.java b/src/main/java/net/openhft/chronicle/testframework/internal/apimetrics/StandardAccumulator1.java index 54f4669f..f921e232 100644 --- a/src/main/java/net/openhft/chronicle/testframework/internal/apimetrics/StandardAccumulator1.java +++ b/src/main/java/net/openhft/chronicle/testframework/internal/apimetrics/StandardAccumulator1.java @@ -9,6 +9,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.stream.Collectors; @@ -100,14 +101,25 @@ public String toString() { .max() .orElse(10); - final String formatting = "%-" + maxCol + "s %12.0f%n"; - - return String.format("*Accumulation per %s*%n", columnName) + + return String.format(Locale.ROOT, "*Accumulation per %s*%n", columnName) + map.entrySet().stream() .sorted(Map.Entry.comparingByKey()) - .map(e -> String.format(formatting, e.getKey(), e.getValue())) + .map(e -> String.format(Locale.ROOT, "%s %12.0f%n", padRight(e.getKey(), maxCol), e.getValue())) .collect(Collectors.joining()) - + String.format(formatting, "_Total_", result()); + + String.format(Locale.ROOT, "%s %12.0f%n", padRight("_Total_", maxCol), result()); + + } + private static String padRight(String value, int width) { + String text = value == null ? "" : value; + if (text.length() >= width) { + return text; + } + StringBuilder builder = new StringBuilder(width); + builder.append(text); + while (builder.length() < width) { + builder.append(' '); + } + return builder.toString(); } } diff --git a/src/main/java/net/openhft/chronicle/testframework/internal/apimetrics/StandardAccumulator2.java b/src/main/java/net/openhft/chronicle/testframework/internal/apimetrics/StandardAccumulator2.java index dd312081..74891ee1 100644 --- a/src/main/java/net/openhft/chronicle/testframework/internal/apimetrics/StandardAccumulator2.java +++ b/src/main/java/net/openhft/chronicle/testframework/internal/apimetrics/StandardAccumulator2.java @@ -97,16 +97,33 @@ public String toString() { .max() .orElse(10); - final String formatting = "%-" + maxCol + "s %-" + maxCol2 + "s %12.0f%n"; - - return String.format("*Accumulation per %s and %s *%n", columnName, columnName2) + + return String.format(Locale.ROOT, "*Accumulation per %s and %s *%n", columnName, columnName2) + map.entrySet().stream() .sorted(comparingByKey()) .flatMap(e -> e.getValue().entrySet().stream() .sorted(comparingByKey()) - .map(e2 -> String.format(formatting, e.getKey(), e2.getKey(), e2.getValue()))) + .map(e2 -> String.format(Locale.ROOT, "%s %s %12.0f%n", + padRight(e.getKey(), maxCol), + padRight(e2.getKey(), maxCol2), + e2.getValue()))) .collect(Collectors.joining()) - + String.format(formatting, "_Total_", "", result()); + + String.format(Locale.ROOT, "%s %s %12.0f%n", + padRight("_Total_", maxCol), + padRight("", maxCol2), + result()); + + } + private static String padRight(String value, int width) { + String text = value == null ? "" : value; + if (text.length() >= width) { + return text; + } + StringBuilder builder = new StringBuilder(width); + builder.append(text); + while (builder.length() < width) { + builder.append(' '); + } + return builder.toString(); } } diff --git a/src/main/java/net/openhft/chronicle/testframework/internal/codestructure/CodeStructureVerifier.java b/src/main/java/net/openhft/chronicle/testframework/internal/codestructure/CodeStructureVerifier.java index 7757433f..526bd601 100644 --- a/src/main/java/net/openhft/chronicle/testframework/internal/codestructure/CodeStructureVerifier.java +++ b/src/main/java/net/openhft/chronicle/testframework/internal/codestructure/CodeStructureVerifier.java @@ -60,7 +60,7 @@ private CodeStructureVerifier(JavaClasses javaClasses, Set rules) { * @throws AssertionError if any of the rules are violated */ public void verify() { - log.info("Running code structure test with the following rules: {}", rules); + log.info("Running code structure test with the following rules: {}", sanitize(rules)); CompositeArchRule compositeArchRule = CompositeArchRule.of(rules); compositeArchRule.check(javaClasses); } @@ -193,7 +193,9 @@ private void skipClasses() { String className = parseClassName(location.asURI()); return !classesToExclude.contains(className); } catch (RuntimeException e) { - log.debug("Failed to parse class name from location: {}", location, e); + final String safeLocation = sanitize(location); + log.debug("Failed to parse class name from location: {}", safeLocation); + log.debug("Location parsing exception", e); return false; } }); @@ -221,4 +223,11 @@ public CodeStructureVerifier build() { } + private static String sanitize(Object value) { + if (value == null) { + return null; + } + return value.toString().replace('\r', ' ').replace('\n', ' '); + } + } diff --git a/src/main/java/net/openhft/chronicle/testframework/internal/codestructure/rules/NonInternalClassesMustNotExtendInternalClassesRuleSupplier.java b/src/main/java/net/openhft/chronicle/testframework/internal/codestructure/rules/NonInternalClassesMustNotExtendInternalClassesRuleSupplier.java index 7f246d8e..40170c28 100644 --- a/src/main/java/net/openhft/chronicle/testframework/internal/codestructure/rules/NonInternalClassesMustNotExtendInternalClassesRuleSupplier.java +++ b/src/main/java/net/openhft/chronicle/testframework/internal/codestructure/rules/NonInternalClassesMustNotExtendInternalClassesRuleSupplier.java @@ -26,17 +26,23 @@ public ArchRule get() { .resideOutsideOfPackage("..internal..") .and() .resideOutsideOfPackage("..impl..") - .should(new ArchCondition("not extend internal classes") { - @Override - public void check(JavaClass javaClass, ConditionEvents events) { - if (javaClass.getSuperclass().isPresent()) { - String fullName = javaClass.getSuperclass().get().getName(); - boolean packageIsInternal = fullName.matches(RuleUtil.INTERNAL_PACKAGE_REGEX); - events.add(new SimpleConditionEvent(javaClass, !packageIsInternal, - String.format("The non-internal class %s extends internal class %s", javaClass.getName(), fullName))); - } - } - }).allowEmptyShould(true); + .should(new NonInternalExtendsInternalCondition()) + .allowEmptyShould(true); } + private static final class NonInternalExtendsInternalCondition extends ArchCondition { + private NonInternalExtendsInternalCondition() { + super("not extend internal classes"); + } + + @Override + public void check(JavaClass javaClass, ConditionEvents events) { + if (javaClass.getSuperclass().isPresent()) { + String fullName = javaClass.getSuperclass().get().getName(); + boolean packageIsInternal = fullName.matches(RuleUtil.INTERNAL_PACKAGE_REGEX); + events.add(new SimpleConditionEvent(javaClass, !packageIsInternal, + String.format("The non-internal class %s extends internal class %s", javaClass.getName(), fullName))); + } + } + } } diff --git a/src/main/java/net/openhft/chronicle/testframework/internal/dto/StandardDtoTester.java b/src/main/java/net/openhft/chronicle/testframework/internal/dto/StandardDtoTester.java index b796e864..31965e02 100644 --- a/src/main/java/net/openhft/chronicle/testframework/internal/dto/StandardDtoTester.java +++ b/src/main/java/net/openhft/chronicle/testframework/internal/dto/StandardDtoTester.java @@ -6,8 +6,6 @@ import org.jetbrains.annotations.NotNull; import java.util.*; -import java.util.function.BiFunction; -import java.util.function.Consumer; import java.util.stream.Collectors; import static java.util.Objects.requireNonNull; @@ -101,6 +99,7 @@ private void checkHashCode() { failed.forEach(n -> System.err.println("WARNING: hashCode() for the mutator " + n + " was not changed")); } + @SuppressWarnings("PMD.EmptyCatchBlock") private void assertValidationRules() { if (builder.validator() == null) // Nothing to assert @@ -128,7 +127,7 @@ private void assertValidationRules() { builder.mandatoryMutators().stream().map(DtoTesterBuilder.AbstractNamedHolderRecord::name).collect(Collectors.joining(", ", "[", "]")) + " but the validator passed without throwing" + " an Exception on using only " + applied + " applied on a fresh instance -> " + t); - } catch (Exception e) { + } catch (RuntimeException e) { // Happy path } namedMutator.mutator().accept(t); @@ -148,6 +147,7 @@ private void assertValidationRules() { } } + @SuppressWarnings("PMD.EmptyCatchBlock") private void assertOptionalsDoesNotPass(@NotNull final Set> set) { requireNonNull(set); final List optionalApplied = newList(); @@ -159,28 +159,12 @@ private void assertOptionalsDoesNotPass(@NotNull final Set> set) builder.validator().accept(optionalTarget); throw new AssertionError("There are at least one mandatory mutator but the validator passed without throwing " + "an Exception on using only optional mutators " + optionalApplied + " applied on a fresh instance -> " + optionalTarget); - } catch (Exception e) { + } catch (RuntimeException e) { // Happy path } } } - private Collection check(final Consumer postMutatorAction, - final BiFunction tester) { - - final T fresh = createInstance(); - final List failed = newList(); - for (NamedMutator namedMutator : builder.allMutators()) { - final T t = createInstance(); - namedMutator.mutator().accept(t); - postMutatorAction.accept(t); - if (tester.apply(fresh, t)) { - failed.add(namedMutator.name()); - } - } - return failed; - } - private T createInstance() { return builder.supplier().get(); } diff --git a/src/main/java/net/openhft/chronicle/testframework/internal/network/proxy/ProxyConnection.java b/src/main/java/net/openhft/chronicle/testframework/internal/network/proxy/ProxyConnection.java index 2c121568..99e9ec95 100644 --- a/src/main/java/net/openhft/chronicle/testframework/internal/network/proxy/ProxyConnection.java +++ b/src/main/java/net/openhft/chronicle/testframework/internal/network/proxy/ProxyConnection.java @@ -6,6 +6,8 @@ import java.io.Closeable; import java.io.IOException; import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; import java.nio.channels.spi.SelectorProvider; @@ -41,20 +43,20 @@ public class ProxyConnection implements Closeable, Runnable { * @param inboundChannel The accepted client channel, closed when the run loop terminates * @param remoteAddress Upstream host and port to connect to */ - public ProxyConnection(SocketChannel inboundChannel, InetSocketAddress remoteAddress) { this.inboundChannel = inboundChannel; - this.remoteAddress = remoteAddress; + this.remoteAddress = new InetSocketAddress(remoteAddress.getHostString(), remoteAddress.getPort()); } @Override public void run() { running = true; - try (final SocketChannel outboundChannel = SelectorProvider.provider().openSocketChannel()) { + try (SocketChannel outboundChannel = SelectorProvider.provider().openSocketChannel()) { outboundChannel.configureBlocking(true); outboundChannel.connect(remoteAddress); LOGGER.info("Established connection between {} and {}", - inboundChannel.socket().getRemoteSocketAddress(), outboundChannel.socket().getRemoteSocketAddress()); + sanitize(inboundChannel.socket().getRemoteSocketAddress()), + sanitize(outboundChannel.socket().getRemoteSocketAddress())); outboundChannel.configureBlocking(false); inboundChannel.configureBlocking(false); while (running) { @@ -66,7 +68,8 @@ public void run() { } } LOGGER.info("Terminating connection between {} and {}", - inboundChannel.socket().getRemoteSocketAddress(), outboundChannel.socket().getRemoteSocketAddress()); + sanitize(inboundChannel.socket().getRemoteSocketAddress()), + sanitize(outboundChannel.socket().getRemoteSocketAddress())); } catch (IOException e) { LOGGER.error("Connection failed", e); } finally { @@ -76,10 +79,10 @@ public void run() { } private void relayTraffic(SocketChannel sourceChannel, SocketChannel destinationChannel) throws IOException { - byteBuffer.clear(); + ((Buffer) byteBuffer).clear(); int read = sourceChannel.read(byteBuffer); if (read > 0) { - byteBuffer.flip(); + ((Buffer) byteBuffer).flip(); destinationChannel.write(byteBuffer); } } @@ -99,4 +102,11 @@ public boolean isFinished() { public void stopForwardingTraffic() { forwardingTraffic = false; } + + private static String sanitize(SocketAddress socketAddress) { + if (socketAddress == null) { + return null; + } + return socketAddress.toString().replace('\r', ' ').replace('\n', ' '); + } } diff --git a/src/main/java/net/openhft/chronicle/testframework/internal/network/proxy/TcpProxy.java b/src/main/java/net/openhft/chronicle/testframework/internal/network/proxy/TcpProxy.java index 17647a37..761d88ac 100644 --- a/src/main/java/net/openhft/chronicle/testframework/internal/network/proxy/TcpProxy.java +++ b/src/main/java/net/openhft/chronicle/testframework/internal/network/proxy/TcpProxy.java @@ -6,6 +6,7 @@ import java.io.Closeable; import java.io.IOException; import java.net.InetSocketAddress; +import java.net.SocketAddress; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.List; @@ -84,7 +85,7 @@ public InetSocketAddress socketAddress() { @Override public void run() { running = true; - LOGGER.info("Starting proxy on {} proxying to {}", socketAddress, connectAddress); + LOGGER.info("Starting proxy on {} proxying to {}", sanitize(socketAddress), sanitize(connectAddress)); try { serverSocket = ServerSocketChannel.open(); serverSocket.bind(socketAddress, 10); @@ -94,10 +95,10 @@ public void run() { if (acceptingNewConnections) { final SocketChannel newConnection = serverSocket.accept(); if (newConnection != null) { - LOGGER.info("Received inbound connection from {}", newConnection.socket().getRemoteSocketAddress()); + LOGGER.info("Received inbound connection from {}", sanitize(newConnection.socket().getRemoteSocketAddress())); final ProxyConnection connection = new ProxyConnection(newConnection, connectAddress); connections.add(connection); - executorService.submit(connection); + executorService.execute(connection); } } for (int i = 0; i < connections.size(); i++) { @@ -111,13 +112,13 @@ public void run() { } pause(10); } - } catch (Exception e) { + } catch (IOException | RuntimeException e) { LOGGER.error("proxy run failed", e); } finally { closeQuietly(serverSocket); isOpen = false; } - LOGGER.info("TCP proxy from {} proxying to {} terminated", socketAddress, connectAddress); + LOGGER.info("TCP proxy from {} proxying to {} terminated", sanitize(socketAddress), sanitize(connectAddress)); finished = true; } @@ -168,4 +169,11 @@ public void close() throws IllegalStateException { public boolean isOpen() { return isOpen; } + + private static String sanitize(SocketAddress address) { + if (address == null) { + return null; + } + return address.toString().replace('\r', ' ').replace('\n', ' '); + } } diff --git a/src/main/java/net/openhft/chronicle/testframework/internal/process/InternalJavaProcessBuilder.java b/src/main/java/net/openhft/chronicle/testframework/internal/process/InternalJavaProcessBuilder.java index f90d4e99..153b5881 100644 --- a/src/main/java/net/openhft/chronicle/testframework/internal/process/InternalJavaProcessBuilder.java +++ b/src/main/java/net/openhft/chronicle/testframework/internal/process/InternalJavaProcessBuilder.java @@ -8,10 +8,9 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; -import java.io.UnsupportedEncodingException; import java.lang.management.ManagementFactory; import java.lang.management.RuntimeMXBean; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; @@ -70,13 +69,12 @@ private static Path findJavaBinPath() { * https://maven.apache.org/surefire/maven-failsafe-plugin/faq.html#corruptedstream */ public static void printProcessOutput(String processName, Process process) { - if (LOGGER.isInfoEnabled()) - LOGGER.info( - String.format("%n Output for %s%n stdout:%n%s stderr:%n%s", - processName, - getProcessStdOut(process), - getProcessStdErr(process)) - ); + if (LOGGER.isInfoEnabled()) { + LOGGER.info("Output for {} | stdout: [{}] | stderr: [{}]", + sanitize(processName), + sanitizeMultiline(getProcessStdOut(process)), + sanitizeMultiline(getProcessStdErr(process))); + } } /** @@ -94,7 +92,7 @@ public static String getProcessStdErr(Process process) { * @param process The process */ public static String getProcessStdOut(Process process) { - return copyStreamToString(process.getErrorStream()); + return copyStreamToString(process.getInputStream()); } /** @@ -112,13 +110,11 @@ private static String copyStreamToString(InputStream inputStream) { os.write(buffer, 0, read); } } catch (IOException e) { - // Ignore - } - try { - return os.toString(Charset.defaultCharset().name()); - } catch (UnsupportedEncodingException e) { - throw new AssertionError(e); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Unable to read process stream", e); + } } + return new String(os.toByteArray(), StandardCharsets.UTF_8); } /** @@ -177,16 +173,16 @@ public Process start() { String className = mainClass.getName(); String javaBin = findJavaBinPath().toString(); - List allArgs = new ArrayList<>(); - allArgs.add(javaBin); - allArgs.addAll(jvmArgsWithoutJavaAgents); - allArgs.add("-Dchronicle.analytics.disable=true"); - allArgs.addAll(Arrays.asList(jvmArguments)); - allArgs.add("-cp"); - allArgs.add(classPath); - allArgs.add(className); - allArgs.addAll(Arrays.asList(programArguments)); - ProcessBuilder processBuilder = new ProcessBuilder(allArgs.toArray(new String[]{})); + List command = new ArrayList<>(); + command.add(javaBin); + command.addAll(jvmArgsWithoutJavaAgents); + command.add("-Dchronicle.analytics.disable=true"); + command.addAll(Arrays.asList(jvmArguments)); + command.add("-cp"); + command.add(classPath); + command.add(className); + command.addAll(Arrays.asList(programArguments)); + ProcessBuilder processBuilder = new ProcessBuilder(command); if (inheritIO) { LOGGER.warn("You've specified to inherit IO when spawning a Java process, this won't play nice with Maven surefire plugin, don't commit this, see https://maven.apache.org/surefire/maven-failsafe-plugin/faq.html#corruptedstream"); processBuilder.inheritIO(); @@ -197,4 +193,18 @@ public Process start() { throw new RuntimeException(e); } } + + private static String sanitize(String value) { + if (value == null) { + return null; + } + return value.replace('\r', ' ').replace('\n', ' '); + } + + private static String sanitizeMultiline(String value) { + if (value == null) { + return null; + } + return value.replace('\r', ' ').replace("\n", " | "); + } } diff --git a/src/main/java/net/openhft/chronicle/testframework/mappedfiles/MappedFileUtil.java b/src/main/java/net/openhft/chronicle/testframework/mappedfiles/MappedFileUtil.java index 116a42f2..829690af 100644 --- a/src/main/java/net/openhft/chronicle/testframework/mappedfiles/MappedFileUtil.java +++ b/src/main/java/net/openhft/chronicle/testframework/mappedfiles/MappedFileUtil.java @@ -7,6 +7,7 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -33,10 +34,6 @@ public enum MappedFileUtil { private static final Pattern LINE_PATTERN = Pattern.compile("([\\p{XDigit}\\-]+)\\s+([rwxsp\\-]+)\\s+(\\p{XDigit}+)\\s+(\\p{XDigit}+:\\p{XDigit}+)\\s+(\\d+)(?:\\s+(.*))?"); // Index constants for the parsed groups. private static final int ADDRESS_INDEX = 1; - private static final int PERMS_INDEX = 2; - private static final int OFFSET_INDEX = 3; - private static final int DEV_INDEX = 4; - private static final int INODE_INDEX = 5; private static final int PATH_INDEX = 6; /** @@ -51,7 +48,7 @@ public static Set getAllMappedFiles() { final Set fileList = new HashSet<>(); if (Files.exists(PROC_SELF_MAPS) && Files.isReadable(PROC_SELF_MAPS)) { - try (final BufferedReader reader = new BufferedReader(new InputStreamReader(Files.newInputStream(PROC_SELF_MAPS)))) { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(Files.newInputStream(PROC_SELF_MAPS), StandardCharsets.UTF_8))) { processProcSelfMaps(fileList, reader); } catch (IOException e) { throw new IllegalStateException("Getting mapped files failed", e); @@ -72,10 +69,10 @@ private static void processProcSelfMaps(Set fileList, BufferedReader rea if (matcher.matches()) { processOneLine(fileList, matcher); } else { - LOGGER.warn("Found non-matching line in /proc/self/maps: {}", line); + LOGGER.warn("Found non-matching line in /proc/self/maps: {}", sanitize(line)); + } } } - } // Processes a single line and adds the file to the list if applicable private static void processOneLine(Set fileList, Matcher matcher) { @@ -86,9 +83,9 @@ private static void processOneLine(Set fileList, Matcher matcher) { if (filename.startsWith("/")) { fileList.add(filename); } else if (!filename.trim().isEmpty()) { - LOGGER.debug("Ignoring non-file {}", filename); + LOGGER.debug("Ignoring non-file {}", sanitize(filename)); + } } - } /** * Parses the provided line using the pattern defined for parsing lines from "/proc/self/maps". @@ -120,4 +117,11 @@ public static String getPath(Matcher matcher) { public static String getAddress(Matcher matcher) { return matcher.group(ADDRESS_INDEX); } + + private static String sanitize(String value) { + if (value == null) { + return null; + } + return value.replace('\r', ' ').replace('\n', ' '); + } } diff --git a/src/main/java11/module-info.java.txt b/src/main/java11/module-info.java.txt index 47ea207f..74c59016 100644 --- a/src/main/java11/module-info.java.txt +++ b/src/main/java11/module-info.java.txt @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 chronicle.software + * Copyright 2016-2025 chronicle.software * * https://chronicle.software * diff --git a/src/test/java/net/openhft/chronicle/testframework/internal/CombinationDemoTest.java b/src/test/java/net/openhft/chronicle/testframework/internal/CombinationDemoTest.java index a7edcf68..820b2895 100644 --- a/src/test/java/net/openhft/chronicle/testframework/internal/CombinationDemoTest.java +++ b/src/test/java/net/openhft/chronicle/testframework/internal/CombinationDemoTest.java @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2006-2020, Speedment, Inc. All Rights Reserved. + * Copyright 2006-2025 chronicle.software * * Licensed 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 diff --git a/src/test/java/net/openhft/chronicle/testframework/internal/CombinationTest.java b/src/test/java/net/openhft/chronicle/testframework/internal/CombinationTest.java index 597bc601..8d7a47f8 100644 --- a/src/test/java/net/openhft/chronicle/testframework/internal/CombinationTest.java +++ b/src/test/java/net/openhft/chronicle/testframework/internal/CombinationTest.java @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2006-2020, Speedment, Inc. All Rights Reserved. + * Copyright 2006-2025 chronicle.software * * Licensed 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 diff --git a/src/test/java/net/openhft/chronicle/testframework/internal/PermutationDemoTest.java b/src/test/java/net/openhft/chronicle/testframework/internal/PermutationDemoTest.java index e8e71426..8fe47177 100644 --- a/src/test/java/net/openhft/chronicle/testframework/internal/PermutationDemoTest.java +++ b/src/test/java/net/openhft/chronicle/testframework/internal/PermutationDemoTest.java @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2006-2020, Speedment, Inc. All Rights Reserved. + * Copyright 2006-2025 chronicle.software * * Licensed 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 diff --git a/src/test/java/net/openhft/chronicle/testframework/internal/PermutationTest.java b/src/test/java/net/openhft/chronicle/testframework/internal/PermutationTest.java index 565e4ae4..50a33706 100644 --- a/src/test/java/net/openhft/chronicle/testframework/internal/PermutationTest.java +++ b/src/test/java/net/openhft/chronicle/testframework/internal/PermutationTest.java @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2006-2020, Speedment, Inc. All Rights Reserved. + * Copyright 2006-2025 chronicle.software * * Licensed 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 diff --git a/src/test/java/net/openhft/chronicle/testframework/internal/ProductDemoTest.java b/src/test/java/net/openhft/chronicle/testframework/internal/ProductDemoTest.java index ee1b03d7..cf9492fb 100644 --- a/src/test/java/net/openhft/chronicle/testframework/internal/ProductDemoTest.java +++ b/src/test/java/net/openhft/chronicle/testframework/internal/ProductDemoTest.java @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2006-2020, Speedment, Inc. All Rights Reserved. + * Copyright 2006-2025 chronicle.software * * Licensed 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 diff --git a/src/test/java/net/openhft/chronicle/testframework/internal/ProductTest.java b/src/test/java/net/openhft/chronicle/testframework/internal/ProductTest.java index aa59d9b5..e7310cd4 100644 --- a/src/test/java/net/openhft/chronicle/testframework/internal/ProductTest.java +++ b/src/test/java/net/openhft/chronicle/testframework/internal/ProductTest.java @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2006-2020, Speedment, Inc. All Rights Reserved. + * Copyright 2006-2025 chronicle.software * * Licensed 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