Back to Repositories

Validating Timestamp Extraction Implementation in NewPipe

This test suite validates the timestamp extraction functionality in NewPipe, focusing on parsing and validating time formats across various patterns and edge cases. The tests ensure robust handling of different timestamp formats and their conversion to Duration objects.

Test Coverage Overview

The test suite provides comprehensive coverage of timestamp extraction scenarios.

Key areas tested include:
  • Simple timestamp validation (HH:MM:SS format)
  • Timestamps with surrounding text
  • Boundary value testing
  • Invalid format handling
  • Edge cases including zero values and maximum limits

Implementation Analysis

The implementation uses JUnit’s Parameterized test runner to efficiently test multiple timestamp scenarios. Each test case validates the TimestampExtractor’s pattern matching and conversion logic, ensuring correct Duration object creation or null handling for invalid inputs.

The testing approach leverages parameterized testing to validate 30+ distinct test cases, including both valid and invalid timestamp formats.

Technical Details

Testing tools and configuration:
  • JUnit 4 testing framework
  • Parameterized test runner (@RunWith(Parameterized.class))
  • Regular expression pattern matching
  • Java Duration API for time representation
  • Custom TimestampMatchDTO for result handling

Best Practices Demonstrated

The test suite exemplifies several testing best practices:

  • Comprehensive boundary testing
  • Data-driven test approach using parameterization
  • Clear test case organization
  • Explicit null handling
  • Descriptive test naming convention
  • Separation of test data from test logic

teamnewpipe/newpipe

app/src/test/java/org/schabi/newpipe/util/external_communication/TimestampExtractorTest.java

            
package org.schabi.newpipe.util.external_communication;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.schabi.newpipe.util.text.TimestampExtractor;

import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;

@RunWith(Parameterized.class)
public class TimestampExtractorTest {

    @Parameterized.Parameter(0)
    public Duration expected;

    @Parameterized.Parameter(1)
    public String stringToProcess;

    @Parameterized.Parameters(name = "Expecting {0} for \"{1}\"")
    public static List<Object[]> dataForTests() {
        return Arrays.asList(new Object[][]{
                // Simple valid values
                {Duration.ofSeconds(1), "0:01"},
                {Duration.ofSeconds(1), "00:01"},
                {Duration.ofSeconds(1), "0:00:01"},
                {Duration.ofSeconds(1), "00:00:01"},
                {Duration.ofMinutes(1).plusSeconds(23), "1:23"},
                {Duration.ofMinutes(1).plusSeconds(23), "01:23"},
                {Duration.ofMinutes(1).plusSeconds(23), "0:01:23"},
                {Duration.ofMinutes(1).plusSeconds(23), "00:01:23"},
                {Duration.ofHours(1).plusMinutes(23).plusSeconds(45), "1:23:45"},
                {Duration.ofHours(1).plusMinutes(23).plusSeconds(45), "01:23:45"},
                // Check with additional text
                {Duration.ofSeconds(1), "Wow 0:01 words"},
                {Duration.ofMinutes(1).plusSeconds(23), "Wow 1:23 words"},
                {Duration.ofSeconds(1), "Wow 0:01 words! 33:"},
                {null, "Wow0:01 abc"},
                {null, "Wow 0:01abc"},
                {null, "Wow0:01abc"},
                {null, "Wow0:01"},
                {null, "0:01abc"},
                // Boundary checks
                {Duration.ofSeconds(0), "0:00"},
                {Duration.ofHours(59).plusMinutes(59).plusSeconds(59), "59:59:59"},
                {null, "60:59:59"},
                {null, "60:59"},
                {null, "0:60"},
                // Format checks
                {null, "000:0"},
                {null, "123:01"},
                {null, "123:123"},
                {null, "2:123"},
                {null, "2:3"},
                {null, "1:2:3"},
                {null, ":3"},
                {null, "01:"},
                {null, ":01"},
                {null, "a:b:c"},
                {null, "abc:def:ghj"},
                {null, "::"},
                {null, ":"},
                {null, ""}
        });
    }

    @Test
    public void testExtract() {
        final Matcher m = TimestampExtractor.TIMESTAMPS_PATTERN.matcher(this.stringToProcess);

        if (!m.find()) {
            if (expected == null) {
                return;
            }
            fail("No match found but expected one");
        }

        final TimestampExtractor.TimestampMatchDTO timestampMatchDTO =
                TimestampExtractor
                        .getTimestampFromMatcher(m, this.stringToProcess);

        if (timestampMatchDTO == null) {
            if (expected == null) {
                return;
            }
            fail("Result shouldn't be null");
        } else if (expected == null) {
            assertNull("Expected that the dto is null, but it isn't", timestampMatchDTO);
            return;
        }

        final int actualSeconds = timestampMatchDTO.seconds();

        assertEquals(expected.getSeconds(), actualSeconds);
    }
}