SonarCloud shows 0.0% coverage on new code, and it's 0.0% coverage on the project as well

111 Views Asked by At

I'm new to CI/CD and doing reports. This is for academic purposes. I need to generate reports on a Maven java project.

I have two test classes one for unit tests the other for integration tests. Each one is in a separate phase by itself. Both jobs are displayed as passed on GitLab Pipeline.

Here's the .gitlab-ci.yml for both tests:

variables:
  SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar"
  GIT_DEPTH: "0"

stages:
  - test
  - verify
  - sonarcloud

unit-tests:
  stage: test
  image: maven:3-openjdk-17
  script:
    - mvn test -Dtest=BookServiceTest # Executes only unit tests excluding integration tests
    - ls -la target/
  artifacts:
    paths:
      - target/surefire-reports
    reports:
      junit: target/surefire-reports/TEST-*.xml

integration-tests:
  stage: test
  image: maven:3-openjdk-17
  script:
    mvn verify -Dit.test=BookRepositoryIT # Executes only integration tests
  artifacts:
    paths:
      - target/failsafe-reports
    reports:
      junit: target/failsafe-reports/TEST-*.xml

Both generate their respective reports (failsafe and surefire) and at the end of tests running I get this in the log:

[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running BookServiceTest
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.121 s - in BookServiceTest
[INFO] 
[INFO] Results:
[INFO] 
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO] 
[INFO] 
[INFO] --- jacoco-maven-plugin:0.8.7:report (report) @ untitled ---
[INFO] Loading execution data file /builds/3il6143305/Indus/target/jacoco.exec
[INFO] Analyzed bundle 'untitled' with 3 classes

Which suggests to me that Jacoco is generating coverage data on the test classes.

Now finally in solarcloud-check job as part of the log I can see this generated verbose:

[INFO] Sensor JaCoCo XML Report Importer [jacoco]
[INFO] Importing 1 report(s). Turn your logs in debug mode in order to see the exhaustive list.
[INFO] Sensor JaCoCo XML Report Importer [jacoco] (done) | time=18ms

Which again tells me that GitLab (or SolarCloud) will successfully read from the jacoco.xml file.

But on the SolarCloud platform I get Quality Gate Failed. And it says I have 2 failing conditions on the new code:

On Maintainability I get : FAILED

B Maintainability Rating on New Code: Required A

And On Coverage I get

0.0% Coverage FAILED 0.0% Coverage: Required ≥ 80.0%

For Maintainability I can understand what I need to fix but for Coverage I really don't get why I'm getting 0.0% Coverage and it's the same for the whole project too.

Edit : I'll add the classes and Test classes as well for reproducibility purposes

public class Book {
    private int id;
    private String title;
    private LocalDate releaseDate;
    /**
     * Constructs a new Book instance with the specified ID, title, and release date.
     *
     * @param id the unique identifier for the book
     * @param title the title of the book
     * @param releaseDate the release date of the book
     */
    public Book(int id, String title, LocalDate releaseDate) {
        this.id = id;
        this.title = title;
        this.releaseDate = releaseDate;
    }

    // Getters and setters
    public int getId() { return id; }
    public void setId(int id) { this.id = id; }
    public String getTitle() { return title; }
    public void setTitle(String title) { this.title = title; }
    public LocalDate getReleaseDate() { return releaseDate; }
    public void setReleaseDate(LocalDate releaseDate) { this.releaseDate = releaseDate; }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Book book = (Book) obj;
        return id == book.id &&
                Objects.equals(title, book.title) &&
                Objects.equals(releaseDate, book.releaseDate);
    }
    @Override
    public int hashCode() {
        return Objects.hash(id, title, releaseDate);
    }
}

package Repository;

import Entity.Book;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;

public class BookRepository {
    private final Connection connection;

    public BookRepository() throws SQLException {
        String url = System.getenv("DATABASE_URL");
        String user = System.getenv("DATABASE_USER");
        String password = System.getenv("DATABASE_PASSWORD");

        this.connection = DriverManager.getConnection(url, user, password);
    }

    public void addBook(Book book) throws SQLException {
        String sql = "INSERT INTO books (id, title, releaseDate) VALUES (?, ?, ?)";
        try (PreparedStatement statement = connection.prepareStatement(sql)) {
            statement.setInt(1, book.getId());
            statement.setString(2, book.getTitle());
            statement.setDate(3, java.sql.Date.valueOf(book.getReleaseDate()));
            statement.executeUpdate();
        }
    }

    public Book getBook(int id) throws SQLException {
        String sql = "SELECT id, title, releaseDate FROM books WHERE id = ?";
        try (PreparedStatement statement = connection.prepareStatement(sql)) {
            statement.setInt(1, id);
            ResultSet resultSet = statement.executeQuery();
            if (resultSet.next()) {
                return new Book(
                        resultSet.getInt("id"),
                        resultSet.getString("title"),
                        resultSet.getDate("releaseDate").toLocalDate()
                );
            }
        }
        return null;
    }

    public void updateBook(Book book) throws SQLException {
        String sql = "UPDATE books SET title = ?, releaseDate = ? WHERE id = ?";
        try (PreparedStatement statement = connection.prepareStatement(sql)) {
            statement.setString(1, book.getTitle());
            statement.setDate(2, java.sql.Date.valueOf(book.getReleaseDate()));
            statement.setInt(3, book.getId());
            statement.executeUpdate();
        }
    }

    public void deleteBook(int id) throws SQLException {
        String sql = "DELETE FROM books WHERE id = ?";
        try (PreparedStatement statement = connection.prepareStatement(sql)) {
            statement.setInt(1, id);
            statement.executeUpdate();
        }
    }

    public List<Book> getAllBooks() throws SQLException {
        List<Book> books = new ArrayList<>();
        String sql = "SELECT id, title, releaseDate FROM books";
        try (Statement statement = connection.createStatement();
             ResultSet resultSet = statement.executeQuery(sql)) {
            while (resultSet.next()) {
                books.add(new Book(
                        resultSet.getInt("id"),
                        resultSet.getString("title"),
                        resultSet.getDate("releaseDate").toLocalDate()
                ));
            }
        }
        return books;
    }

    public void createTable() throws SQLException {
        try (Statement statement = connection.createStatement()) {
            statement.execute("DROP TABLE IF EXISTS books");
            statement.execute("CREATE TABLE books (" +
                    "id INT PRIMARY KEY, " +
                    "title VARCHAR(255), " +
                    "releaseDate DATE)");
        }
    }

    public void resetTable() throws SQLException {
        try (Statement statement = connection.createStatement()) {
            statement.execute("DELETE FROM books");
        }
    }
}

package Service;

import Entity.Book;
import Repository.BookRepository;

import java.sql.SQLException;
import java.util.List;

public class BookService {
    private final BookRepository bookRepository;

    public BookService(BookRepository bookRepository) {
        this.bookRepository = bookRepository;
    }

    public void addBook(Book book) throws SQLException {
        bookRepository.addBook(book);
    }

    public Book getBook(int id) throws SQLException {
        return bookRepository.getBook(id);
    }

    public void updateBook(Book book) throws SQLException {
        bookRepository.updateBook(book);
    }

    public void deleteBook(int id) throws SQLException {
        bookRepository.deleteBook(id);
    }

    public List<Book> getAllBooks() throws SQLException {
        return bookRepository.getAllBooks();
    }
}

Unit Tests here :

import org.junit.Test;

import java.time.LocalDate;

public class BookServiceTest {

    private void assertReleaseDateNotFuture(LocalDate releaseDate) {
        LocalDate currentDate = LocalDate.now();
        if (releaseDate.isAfter(currentDate)) {
            throw new IllegalArgumentException("Release date should not be in the future.");
        }
    }

    @Test(expected = IllegalArgumentException.class)
    public void testReleaseDateNotInFuture() {
        LocalDate futureDate = LocalDate.now().plusDays(1); // One day in the future
        assertReleaseDateNotFuture(futureDate); // This should throw an exception
    }
}

Integration Tests here :

import Entity.Book;
import Repository.BookRepository;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.Assert;

import java.sql.SQLException;
import java.time.LocalDate;
import java.util.List;

public class BookRepositoryIT {
    private BookRepository bookRepository;

    @Before
    public void setUp() throws SQLException {
        bookRepository = new BookRepository();
        bookRepository.createTable(); // Create the books table
    }

    @After
    public void tearDown() throws SQLException {
        bookRepository.resetTable(); // Reset the books table
    }

    @Test
    public void testAddAndGetBook() throws SQLException {
        LocalDate releaseDate = LocalDate.now();
        Book newBook = new Book(1, "Test Book", releaseDate);
        bookRepository.addBook(newBook);

        Book retrievedBook = bookRepository.getBook(1);
        Assert.assertNotNull(retrievedBook);
        Assert.assertEquals("Test Book", retrievedBook.getTitle());
        Assert.assertEquals(releaseDate, retrievedBook.getReleaseDate());
    }

    @Test
    public void testUpdateBook() throws SQLException {
        LocalDate releaseDate = LocalDate.now();
        Book bookToUpdate = new Book(2, "Original Title", releaseDate);
        bookRepository.addBook(bookToUpdate);

        bookToUpdate.setTitle("Updated Title");
        bookRepository.updateBook(bookToUpdate);

        Book updatedBook = bookRepository.getBook(2);
        Assert.assertNotNull(updatedBook);
        Assert.assertEquals("Updated Title", updatedBook.getTitle());
    }

    @Test
    public void testDeleteBook() throws SQLException {
        LocalDate releaseDate = LocalDate.now();
        Book bookToDelete = new Book(3, "Delete Title", releaseDate);
        bookRepository.addBook(bookToDelete);

        bookRepository.deleteBook(3);
        Book deletedBook = bookRepository.getBook(3);
        Assert.assertNull(deletedBook);
    }

    @Test
    public void testGetAllBooks() throws SQLException {
        LocalDate releaseDate = LocalDate.now();
        bookRepository.addBook(new Book(4, "Book 1", releaseDate));
        bookRepository.addBook(new Book(5, "Book 2", releaseDate));

        List<Book> allBooks = bookRepository.getAllBooks();
        Assert.assertTrue(allBooks.size() >= 2);

        for (Book book : allBooks) {
            Assert.assertTrue(book.getTitle().equals("Book 1") || book.getTitle().equals("Book 2"));
        }
    }
}

pom.xml is a bit lenghty but here you go:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>untitled</artifactId>
    <version>1.0-SNAPSHOT</version>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.22.2</version> <!-- Use the latest version available -->
                <configuration>
                    <reportsDirectory>${project.build.directory}/surefire-reports</reportsDirectory>
                </configuration>

            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-checkstyle-plugin</artifactId>
                <version>3.1.2</version> <!-- Use the latest version available -->
                <configuration>
                    <configLocation>google_checks.xml</configLocation> <!-- Google's style guide -->
                    <consoleOutput>true</consoleOutput>
                    <outputFile>${project.build.directory}/checkstyle-result.xml</outputFile> <!-- Checkstyle results in XML format -->
                </configuration>
                <executions>
                    <execution>
                        <phase>validate</phase>
                        <goals>
                            <goal>check</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-failsafe-plugin</artifactId>
                <version>2.22.2</version> <!-- Use the latest version available -->
                <executions>
                    <execution>
                        <goals>
                            <goal>integration-test</goal>
                            <goal>verify</goal>
                        </goals>
                        <configuration>
                            <!-- Specify patterns for integration test classes -->
                            <reportsDirectory>${project.build.directory}/failsafe-reports</reportsDirectory>
                            <includes>
                                <include>**/*IT.java</include>
                            </includes>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.jacoco</groupId>
                <artifactId>jacoco-maven-plugin</artifactId>
                <version>0.8.7</version> <!-- Use the latest version available -->
                <executions>
                    <execution>
                        <goals>
                            <goal>prepare-agent</goal> <!-- Prepares JaCoCo to measure coverage -->
                        </goals>
                    </execution>
                    <execution>
                        <id>report</id>
                        <phase>test</phase> <!-- Bind report goal to a lifecycle phase -->
                        <goals>
                            <goal>report</goal> <!-- Creates code coverage report -->
                        </goals>
                        <configuration>
                            <outputDirectory>${project.build.directory}/site/jacoco</outputDirectory>
                            <dataFile>${project.build.directory}/jacoco.exec</dataFile>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <sonar.organization>3il6143305</sonar.organization>
        <sonar.java.checkstyle.reportPaths>${project.build.directory}/checkstyle-result.xml</sonar.java.checkstyle.reportPaths>
        <sonar.coverage.jacoco.xmlReportPaths>${project.build.directory}/site/jacoco/jacoco.xml</sonar.coverage.jacoco.xmlReportPaths>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-core</artifactId>
            <version>3.11.2</version> <!-- Use the latest version available -->
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.1</version>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>2.2.224</version>
        </dependency>
        <dependency>
            <groupId>com.puppycrawl.tools</groupId>
            <artifactId>checkstyle</artifactId>
            <version>10.13.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.23</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-jpa</artifactId>
            <version>2.7.5</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>5.3.23</version>
        </dependency>

    </dependencies>

</project>

And finally I forgot to put the sonarcloud check scripts so here you go:

sonarcloud-check:
  stage: sonarcloud
  image: maven:3-openjdk-17
  cache:
    key: "${CI_JOB_NAME}"
    paths:
      - .sonar/cache
  script:
    - mvn clean verify sonar:sonar -Dsonar.projectKey=3il6143305_Indus -Dsonar.host.url=https://sonarcloud.io -Dsonar.login=$SONAR_TOKEN -Dsonar.coverage.jacoco.xmlReportPaths=target/site/jacoco/jacoco.xml
  only:
    - merge_requests
    - master
    - develop
    - main
0

There are 0 best solutions below