Skip to content

fix: Cross-platform timestamp test failures due to OS timestamp resolution differences #78

@dfcoffin

Description

@dfcoffin

Problem Description

Multiple tests are passing on macOS but failing on Windows due to differences in operating system timestamp resolution. The tests use isAfter() assertions to verify that updated timestamps are strictly greater than created or previous updated timestamps. On Windows, the timestamp resolution may be coarser, causing both timestamps to have the same value and making isAfter() assertions fail.

Affected Tests

Failing on Windows (5 tests):

  1. IdentifiedObjectTest.java:144

    • Test: shouldUpdateModificationTimestampOnEntityUpdate
    • Location: src/test/java/org/greenbuttonalliance/espi/common/domain/common/IdentifiedObjectTest.java
    • Assertion: assertThat(updated.getUpdated()).isAfter(originalUpdated);
  2. UsageSummaryRepositoryTest.java:539

    • Test: shouldUpdateTimestampsOnModification
    • Location: src/test/java/org/greenbuttonalliance/espi/common/repositories/usage/UsageSummaryRepositoryTest.java
    • Assertion: assertThat(retrieved.get().getUpdated()).isAfter(retrieved.get().getCreated());
  3. ResourceRepositoryTest.java:238

    • Test: Timestamp update verification in resource test
    • Location: src/test/java/org/greenbuttonalliance/espi/common/repositories/usage/ResourceRepositoryTest.java
    • Assertion: assertThat(retrievedResource.getUpdated()).isAfter(retrievedResource.getCreated());
  4. CustomerAccountRepositoryTest.java:593

    • Test: shouldUpdateTimestampsOnModification
    • Location: src/test/java/org/greenbuttonalliance/espi/common/repositories/customer/CustomerAccountRepositoryTest.java
    • Assertion: assertThat(retrieved.get().getUpdated()).isAfter(retrieved.get().getCreated());
  5. MeterRepositoryTest.java:615

    • Test: shouldUpdateTimestampsOnModification
    • Location: src/test/java/org/greenbuttonalliance/espi/common/repositories/customer/MeterRepositoryTest.java
    • Assertion: assertThat(retrieved.get().getUpdated()).isAfter(retrieved.get().getCreated());

Potentially Affected (date range logic, needs verification):

  1. StatementRepositoryTest.java:200

    • Test: Date range filtering with isAfter() on business logic fields
    • Assertion: assertThat(results).allMatch(s -> s.getIssueDateTime().isAfter(cutoffDate));
  2. StatementRepositoryTest.java:267

    • Test: Date range filtering
    • Assertion: s.getIssueDateTime().isAfter(startDate) && s.getIssueDateTime().isBefore(endDate)

Root Cause

Timestamp Resolution Differences:

  • macOS/Linux: Typically microsecond or nanosecond resolution
  • Windows: May have millisecond resolution or use time slicing that results in identical timestamps

Test Pattern:

// Current problematic pattern:
UsageSummaryEntity saved = persistAndFlush(summary);
saved.setDescription("Updated");
UsageSummaryEntity updated = usageSummaryRepository.save(saved);
flushAndClear();

// Fails on Windows when both timestamps are identical:
assertThat(retrieved.get().getUpdated()).isAfter(retrieved.get().getCreated());

On Windows, both created and updated timestamps may have identical values when operations occur within the same time slice, causing isAfter() to return false.

Recommended Solution

Replace isAfter() with isAfterOrEqualTo() in all timestamp update tests. This change:

  • ✅ Maintains the test's intent (verifying timestamp is set/updated)
  • ✅ Works consistently across all platforms
  • ✅ Still validates that JPA @PreUpdate callbacks are functioning
  • ✅ Accounts for the reality that updates can occur within the same timestamp unit

Example Fix:

// Before (fails on Windows):
assertThat(updated.getUpdated()).isAfter(originalUpdated);

// After (works on all platforms):
assertThat(updated.getUpdated()).isAfterOrEqualTo(originalUpdated);

For tests comparing updated vs created:

// Before (fails on Windows):
assertThat(retrieved.get().getUpdated()).isAfter(retrieved.get().getCreated());

// After (works on all platforms):
assertThat(retrieved.get().getUpdated()).isAfterOrEqualTo(retrieved.get().getCreated());

Implementation Plan

  1. Update IdentifiedObjectTest.java (highest priority - base class test)

    • Line 144: Change isAfter(originalUpdated) to isAfterOrEqualTo(originalUpdated)
  2. Update Repository Tests (timestamp update verification tests)

    • UsageSummaryRepositoryTest.java:539
    • ResourceRepositoryTest.java:238
    • CustomerAccountRepositoryTest.java:593
    • MeterRepositoryTest.java:615
    • Change all isAfter(created) to isAfterOrEqualTo(created)
  3. Review StatementRepositoryTest.java (date range tests)

    • Lines 200, 267: Analyze if these business logic tests are affected
    • These may be testing different behavior (date filtering) and might not need changes
  4. Verify All Tests Pass

    • Run tests on macOS: mvn test
    • Run tests on Windows: mvn test
    • Confirm all 533 tests pass on both platforms

Additional Context

  • Test framework: JUnit 5 with AssertJ assertions
  • ORM: Hibernate 6.x with JPA @PrePersist and @PreUpdate callbacks
  • Database: H2 (test), MySQL 8.0+, PostgreSQL 15+
  • All affected entities extend IdentifiedObject which has automatic timestamp management

Acceptance Criteria

  • All 5 timestamp update tests pass on both macOS and Windows
  • Test assertions still validate timestamp update behavior
  • No functional changes to entity timestamp behavior
  • Tests remain clear and maintainable
  • StatementRepository date range tests verified (may not need changes)

Related Information

  • Similar cross-platform issues: This is a common problem with timestamp-based tests across operating systems
  • AssertJ documentation: https://assertj.github.io/doc/#assertj-core-time
  • Hibernate timestamp management: @CreationTimestamp, @UpdateTimestamp annotations

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions