Build and Test

This page explains how to build Fess, run tests, and create distribution packages.

Build System Overview

Fess uses Maven as its build tool. Maven automates dependency management, compilation, testing, and packaging.

pom.xml

This is Maven’s configuration file, located in the project root directory.

Main configuration contents:

  • Project information (groupId, artifactId, version)

  • Dependency libraries

  • Build plugins

  • Profiles

Basic Build Commands

Clean Build

Remove build artifacts and rebuild:

mvn clean compile

Creating Packages

Create an executable JAR file:

mvn package

Artifacts are generated in the target/ directory:

target/
├── fess-15.3.x.jar
└── fess-15.3.x/

Full Build

Execute all: clean, compile, test, and package:

mvn clean package

Downloading Dependencies

Download dependency libraries:

mvn dependency:resolve

Downloading OpenSearch Plugins

Download OpenSearch and required plugins:

mvn antrun:run

Note

Execute this command when setting up the development environment or when updating plugins.

Testing

Fess uses JUnit for implementing tests.

Running Unit Tests

Running All Unit Tests

mvn test

Running Specific Test Classes

mvn test -Dtest=SearchServiceTest

Running Specific Test Methods

mvn test -Dtest=SearchServiceTest#testSearch

Running Multiple Test Classes

mvn test -Dtest=SearchServiceTest,CrawlerTest

Skipping Tests

To build without running tests:

mvn package -DskipTests

Warning

Do not skip tests during development. Before creating a PR, ensure all tests pass.

Running Integration Tests

Run all tests including integration tests:

mvn verify

Writing Tests

Creating Unit Tests

Test Class Placement

Place test classes under src/test/java/. Use the same package structure as the main code.

src/
├── main/java/org/codelibs/fess/app/service/SearchService.java
└── test/java/org/codelibs/fess/app/service/SearchServiceTest.java

Basic Test Class Structure

package org.codelibs.fess.app.service;

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

public class SearchServiceTest {

    @Test
    public void testSearch() {
        // Given: Test preconditions
        SearchService service = new SearchService();
        String query = "test";

        // When: Execute test target
        SearchResponse response = service.search(query);

        // Then: Verify results
        assertNotNull(response);
        assertTrue(response.getResultCount() > 0);
    }
}

Test Lifecycle

import org.junit.jupiter.api.*;

public class MyServiceTest {

    @BeforeAll
    static void setUpClass() {
        // Execute once before all tests
    }

    @BeforeEach
    void setUp() {
        // Execute before each test
    }

    @Test
    void testSomething() {
        // Test
    }

    @AfterEach
    void tearDown() {
        // Execute after each test
    }

    @AfterAll
    static void tearDownClass() {
        // Execute once after all tests
    }
}

Assertions

Use JUnit 5 assertions:

import static org.junit.jupiter.api.Assertions.*;

// Equality
assertEquals(expected, actual);
assertNotEquals(unexpected, actual);

// null check
assertNull(obj);
assertNotNull(obj);

// Boolean
assertTrue(condition);
assertFalse(condition);

// Exception
assertThrows(IllegalArgumentException.class, () -> {
    service.doSomething();
});

// Collection
assertIterableEquals(expectedList, actualList);

Using Mocks

Create mocks using Mockito:

import static org.mockito.Mockito.*;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.junit.jupiter.api.extension.ExtendWith;

@ExtendWith(MockitoExtension.class)
public class SearchServiceTest {

    @Mock
    private SearchEngineClient searchEngineClient;

    @Test
    public void testSearch() {
        // Setup mock
        when(searchEngineClient.search(anyString()))
            .thenReturn(new SearchResponse());

        // Execute test
        SearchService service = new SearchService();
        service.setSearchEngineClient(searchEngineClient);
        SearchResponse response = service.search("test");

        // Verify
        assertNotNull(response);
        verify(searchEngineClient, times(1)).search("test");
    }
}

Test Coverage

Measure test coverage with JaCoCo:

mvn clean test jacoco:report

The report is generated at target/site/jacoco/index.html.

Code Quality Checks

Checkstyle

Check coding style:

mvn checkstyle:check

The configuration file is checkstyle.xml.

SpotBugs

Detect potential bugs:

mvn spotbugs:check

PMD

Detect code quality issues:

mvn pmd:check

Running All Checks

mvn clean verify checkstyle:check spotbugs:check pmd:check

Creating Distribution Packages

Creating Release Packages

Create packages for distribution:

mvn clean package

Generated artifacts:

target/releases/
├── fess-15.3.x.tar.gz      # For Linux/macOS
├── fess-15.3.x.zip         # For Windows
├── fess-15.3.x.rpm         # RPM package
└── fess-15.3.x.deb         # DEB package

Creating Docker Images

Create a Docker image:

mvn package docker:build

Generated image:

docker images | grep fess

Profiles

Maven profiles allow you to apply different configurations for different environments.

Development Profile

Build with development settings:

mvn package -Pdev

Production Profile

Build with production settings:

mvn package -Pprod

Fast Build

Build quickly by skipping tests and checks:

mvn package -Pfast

Warning

Fast build is for development verification only. Before creating a PR, always execute a full build.

CI/CD

Fess uses GitHub Actions for CI/CD.

GitHub Actions

Configuration files are in the .github/workflows/ directory.

Automatically executed checks:

  • Build

  • Unit tests

  • Integration tests

  • Code style checks

  • Code quality checks

Local CI Checks

Before creating a PR, you can run checks similar to CI locally:

mvn clean verify checkstyle:check

Troubleshooting

Build Errors

Error: Failed to download dependencies

# Clear Maven local repository
rm -rf ~/.m2/repository
mvn clean compile

Error: Out of memory

# Increase Maven memory
export MAVEN_OPTS="-Xmx2g -XX:MaxPermSize=512m"
mvn clean package

Error: Java version is old

Use Java 21 or later:

java -version

Test Errors

Tests timeout

Extend test timeout:

mvn test -Dmaven.test.timeout=600

OpenSearch won’t start

Check ports and change if in use:

lsof -i :9201

Dependency Issues

Dependency conflicts

Check dependency tree:

mvn dependency:tree

Exclude specific dependencies:

<dependency>
    <groupId>org.example</groupId>
    <artifactId>example-lib</artifactId>
    <version>1.0</version>
    <exclusions>
        <exclusion>
            <groupId>conflicting-lib</groupId>
            <artifactId>conflicting-lib</artifactId>
        </exclusion>
    </exclusions>
</dependency>

Build Best Practices

Regular Clean Builds

Regularly perform clean builds to avoid build cache issues:

mvn clean package

Running Tests

Always run tests before committing:

mvn test

Code Quality Checks

Check code quality before creating a PR:

mvn checkstyle:check spotbugs:check

Updating Dependencies

Regularly update dependencies:

mvn versions:display-dependency-updates

Leveraging Build Cache

Leverage Maven cache to reduce build time:

# Skip if already compiled
mvn compile

Maven Command Reference

Frequently Used Commands

# Clean
mvn clean

# Compile
mvn compile

# Test
mvn test

# Package
mvn package

# Install (register to local repository)
mvn install

# Verify (including integration tests)
mvn verify

# Resolve dependencies
mvn dependency:resolve

# Display dependency tree
mvn dependency:tree

# Display project information
mvn help:effective-pom

Next Steps

After understanding build and test methods, refer to the following documents:

References