This is an automated email from the ASF dual-hosted git repository.

shuber pushed a commit to branch unomi-3-dev
in repository https://gitbox.apache.org/repos/asf/unomi.git


The following commit(s) were added to refs/heads/unomi-3-dev by this push:
     new eded9dfcc UNOMI-881 Remove unused `CONFIGURATION_UPDATE_ANALYSIS.md`, 
adjust logging detail levels, and refine test utilities and progress display 
logic.
eded9dfcc is described below

commit eded9dfcc48636dd5c816b6e90b2a8bbccb58d64
Author: Serge Huber <[email protected]>
AuthorDate: Thu Jan 1 15:15:16 2026 +0100

    UNOMI-881 Remove unused `CONFIGURATION_UPDATE_ANALYSIS.md`, adjust logging 
detail levels, and refine test utilities and progress display logic.
---
 CONFIGURATION_UPDATE_ANALYSIS.md                   | 504 ---------------------
 .../org/apache/unomi/itests/ProgressListener.java  |  80 +++-
 services-common/pom.xml                            |  10 +
 .../impl/scheduler/SchedulerServiceImpl.java       |  32 +-
 .../impl/scheduler/TaskExecutionManager.java       |  14 +-
 .../impl/scheduler/SchedulerServiceImplTest.java   |  22 +-
 tracing/tracing-impl/pom.xml                       |   4 +-
 7 files changed, 117 insertions(+), 549 deletions(-)

diff --git a/CONFIGURATION_UPDATE_ANALYSIS.md b/CONFIGURATION_UPDATE_ANALYSIS.md
deleted file mode 100644
index 71eaa74f7..000000000
--- a/CONFIGURATION_UPDATE_ANALYSIS.md
+++ /dev/null
@@ -1,504 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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 the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-//
-
-# Configuration Update Analysis: ElasticSearch and OpenSearch Persistence
-
-## Problem Statement
-
-The current `updated()` method in both `ElasticSearchPersistenceServiceImpl` 
and `OpenSearchPersistenceServiceImpl` only updates in-memory configuration 
values. It does **not** update the corresponding infrastructure components 
(index templates, rollover policies, lifecycle policies, etc.) that were 
created during initialization based on these configuration values.
-
-### Example: rollover.indices Change
-
-When `rollover.indices` changes (e.g., from `"event,session"` to 
`"event,session,profile"`), the system needs to:
-1. **Create Index Templates**: New rollover templates must be created for 
newly added item types (e.g., `profile`)
-2. **Update OpenSearch ISM Policy**: The ISM policy's 
`ism_template.index_patterns` must be updated to include patterns for new item 
types
-3. **Handle Removed Item Types**: Templates for removed item types may need 
cleanup (though existing indices will continue to work)
-
-However, the current `updated()` method only calls `setRolloverIndices()`, 
which updates the in-memory value but does not:
-- Create rollover templates for newly added item types
-- Update the OpenSearch ISM policy template patterns
-- Remove templates for item types no longer in the list (optional)
-
-## Configuration Properties Analysis
-
-### Infrastructure-Affecting Properties
-
-These properties affect infrastructure components that need to be updated when 
changed:
-
-#### 1. `indexPrefix`
-**Used in:**
-- Rollover alias: `indexPrefix + "-" + itemName`
-- Index names: `indexPrefix + "-" + itemType`
-- Rollover index patterns: `indexPrefix + "-" + itemType + "-*"`
-- Lifecycle policy name: `indexPrefix + "-" + ROLLOVER_LIFECYCLE_NAME` (ES) or 
`indexPrefix + "-rollover-lifecycle-policy"` (OS)
-- Index template names: `rolloverAlias + "-rollover-template"`
-
-**Infrastructure Impact:**
-- ✅ **Index Templates**: Template names and index patterns depend on 
`indexPrefix`
-- ✅ **Rollover Lifecycle Policies**: Policy names depend on `indexPrefix`
-- ✅ **Existing Indices**: Index names depend on `indexPrefix` (but existing 
indices cannot be renamed)
-- ⚠️ **Note**: Changing `indexPrefix` effectively creates a new namespace. 
Existing indices won't be affected, but new operations will use the new prefix.
-
-**Update Required:**
-- Re-register rollover lifecycle policy (with new name)
-- Recreate index templates for rollover indices (with new names/patterns)
-- Update rollover alias references in template settings
-
-#### 2. `rolloverIndexNumberOfShards`
-**Used in:**
-- Index template settings for rollover indices
-- New rollover index creation
-
-**Infrastructure Impact:**
-- ✅ **Index Templates**: Settings contain `number_of_shards`
-- ⚠️ **Existing Indices**: Cannot be changed on existing indices (only affects 
new indices created from template)
-
-**Update Required:**
-- Update index template settings for all rollover item types
-
-#### 3. `rolloverIndexNumberOfReplicas`
-**Used in:**
-- Index template settings for rollover indices
-- New rollover index creation
-
-**Infrastructure Impact:**
-- ✅ **Index Templates**: Settings contain `number_of_replicas`
-- ⚠️ **Existing Indices**: Can be updated via index settings API (affects 
existing indices)
-
-**Update Required:**
-- Update index template settings for all rollover item types
-- Optionally update existing rollover indices (if desired)
-
-#### 4. `rolloverIndexMappingTotalFieldsLimit`
-**Used in:**
-- Index template settings for rollover indices
-
-**Infrastructure Impact:**
-- ✅ **Index Templates**: Settings contain `mapping.total_fields.limit`
-- ⚠️ **Existing Indices**: Cannot be changed on existing indices (only affects 
new indices)
-
-**Update Required:**
-- Update index template settings for all rollover item types
-
-#### 5. `rolloverIndexMaxDocValueFieldsSearch`
-**Used in:**
-- Index template settings for rollover indices
-
-**Infrastructure Impact:**
-- ✅ **Index Templates**: Settings contain `max_docvalue_fields_search`
-- ⚠️ **Existing Indices**: Cannot be changed on existing indices (only affects 
new indices)
-
-**Update Required:**
-- Update index template settings for all rollover item types
-
-#### 6. `rolloverMaxSize`
-**Used in:**
-- Rollover lifecycle policy rollover action
-
-**Infrastructure Impact:**
-- ✅ **Rollover Lifecycle Policy**: Policy contains rollover action with 
`maxSize`
-
-**Update Required:**
-- Re-register rollover lifecycle policy
-
-#### 7. `rolloverMaxAge`
-**Used in:**
-- Rollover lifecycle policy rollover action
-
-**Infrastructure Impact:**
-- ✅ **Rollover Lifecycle Policy**: Policy contains rollover action with 
`maxAge`
-
-**Update Required:**
-- Re-register rollover lifecycle policy
-
-#### 8. `rolloverMaxDocs`
-**Used in:**
-- Rollover lifecycle policy rollover action
-
-**Infrastructure Impact:**
-- ✅ **Rollover Lifecycle Policy**: Policy contains rollover action with 
`maxDocs`
-
-**Update Required:**
-- Re-register rollover lifecycle policy
-
-#### 9. `rolloverIndices` (Configuration: `rollover.indices`)
-**Used in:**
-- Determines which item types use rollover (checked via 
`isItemTypeRollingOver(itemType)`)
-- Controls whether `internalCreateRolloverTemplate()` is called during index 
creation
-- **OpenSearch only**: ISM policy's `ism_template.index_patterns` contains 
patterns for each item type in the list
-
-**Infrastructure Impact:**
-- ✅ **Index Templates**: Templates are only created for item types in 
`rolloverIndices` list
-  - When an item type is added: A rollover template must be created for that 
item type
-  - When an item type is removed: The template may remain but won't be used 
for new indices
-- ✅ **OpenSearch ISM Policy**: The `ism_template.index_patterns` array must 
match the current `rolloverIndices` list
-  - Each item type in the list should have a corresponding pattern: 
`indexPrefix + "-" + itemType + "-*"`
-- ⚠️ **Existing Indices**: Existing indices are not affected, but new indices 
will follow the new configuration
-
-**Update Required:**
-- **For newly added item types:**
-  - Create rollover index template (call 
`internalCreateRolloverTemplate(itemType)`)
-  - Ensure mapping exists for the item type
-- **For OpenSearch:**
-  - Re-register rollover lifecycle policy to update 
`ism_template.index_patterns`
-- **For removed item types (optional):**
-  - Optionally remove rollover templates (though keeping them doesn't cause 
issues)
-
-**Example Scenario:**
-- **Before**: `rollover.indices = "event,session"`
-- **After**: `rollover.indices = "event,session,profile"`
-- **Required Actions:**
-  1. Create rollover template for `profile` item type
-  2. Update OpenSearch ISM policy to include pattern for `profile` indices
-  3. When `profile` index is first created, it will use rollover instead of 
regular index creation
-
-#### 10. `numberOfShards` (non-rollover)
-**Used in:**
-- Non-rollover index creation
-
-**Infrastructure Impact:**
-- ⚠️ **Existing Indices**: Cannot be changed on existing indices (only affects 
new indices)
-
-**Update Required:**
-- None (only affects new indices created after change)
-
-#### 11. `numberOfReplicas` (non-rollover)
-**Used in:**
-- Non-rollover index creation
-
-**Infrastructure Impact:**
-- ✅ **Existing Indices**: Can be updated via index settings API
-
-**Update Required:**
-- Optionally update existing non-rollover indices (if desired)
-
-#### 12. `indexMappingTotalFieldsLimit` (non-rollover)
-**Used in:**
-- Non-rollover index creation
-
-**Infrastructure Impact:**
-- ⚠️ **Existing Indices**: Cannot be changed on existing indices (only affects 
new indices)
-
-**Update Required:**
-- None (only affects new indices created after change)
-
-#### 13. `indexMaxDocValueFieldsSearch` (non-rollover)
-**Used in:**
-- Non-rollover index creation
-
-**Infrastructure Impact:**
-- ⚠️ **Existing Indices**: Cannot be changed on existing indices (only affects 
new indices)
-
-**Update Required:**
-- None (only affects new indices created after change)
-
-### Non-Infrastructure Properties
-
-These properties only affect runtime behavior and don't require infrastructure 
updates:
-- `throwExceptions`
-- `alwaysOverwrite`
-- `useBatchingForSave`
-- `useBatchingForUpdate`
-- `aggQueryThrowOnMissingDocs`
-- `logLevelRestClient`
-- `clientSocketTimeout`
-- `taskWaitingTimeout`
-- `taskWaitingPollingInterval`
-- `aggQueryMaxResponseSizeHttp`
-- `aggregateQueryBucketSize`
-- `itemTypeToRefreshPolicy`
-
-## Current Initialization Flow
-
-### ElasticSearch
-1. `start()` method calls:
-   - `registerRolloverLifecyclePolicy()` - Creates ILM policy with name 
`indexPrefix + "-" + ROLLOVER_LIFECYCLE_NAME`
-   - `loadPredefinedMappings()` - For each mapping:
-     - If item type is in `rolloverIndices`: calls 
`internalCreateRolloverTemplate()` and `internalCreateRolloverIndex()`
-     - Otherwise: calls `internalCreateIndex()`
-
-### OpenSearch
-1. `start()` method calls:
-   - `registerRolloverLifecyclePolicy()` - Creates ISM policy with name 
`indexPrefix + "-rollover-lifecycle-policy"`
-   - `loadPredefinedMappings()` - For each mapping:
-     - If item type is in `rolloverIndices`: calls 
`internalCreateRolloverTemplate()` and `internalCreateRolloverIndex()`
-     - Otherwise: calls `internalCreateIndex()`
-
-## Required Updates for Configuration Changes
-
-### Minimal Change Solution Approach
-
-The solution should:
-1. **Track previous configuration values** to detect what changed
-2. **Identify affected infrastructure** based on changed properties
-3. **Update infrastructure components** only when necessary
-4. **Minimize disruption** by only updating what's needed
-
-### Update Strategy by Property
-
-#### For `indexPrefix`:
-- **Action**: Re-register lifecycle policy, recreate all rollover templates
-- **Reason**: Policy and template names depend on prefix
-- **Note**: Existing indices keep old prefix (cannot rename indices)
-
-#### For `rolloverIndexNumberOfShards`, `rolloverIndexNumberOfReplicas`, 
`rolloverIndexMappingTotalFieldsLimit`, `rolloverIndexMaxDocValueFieldsSearch`:
-- **Action**: Update index template settings for all rollover item types
-- **Method**: Use `PutIndexTemplateRequest` with updated settings
-- **Note**: Only affects new indices created from template
-
-#### For `rolloverMaxSize`, `rolloverMaxAge`, `rolloverMaxDocs`:
-- **Action**: Re-register rollover lifecycle policy
-- **Method**: Call `registerRolloverLifecyclePolicy()` again
-- **Note**: Policy is updated in-place (same name)
-
-#### For `rolloverIndices`:
-- **Action**: 
-  - **Detect changes**: Compare old and new lists to find added/removed item 
types
-  - **For newly added item types**:
-    - Verify mapping exists for the item type
-    - Create rollover index template (call 
`internalCreateRolloverTemplate(itemType)`)
-  - **For OpenSearch**: Re-register lifecycle policy to update 
`ism_template.index_patterns`
-  - **For removed item types** (optional):
-    - Optionally remove rollover templates (though keeping them doesn't cause 
issues)
-- **Method**: 
-  - Parse comma-separated list and compare with previous list
-  - For each new item type: `internalCreateRolloverTemplate(itemType)` (if 
mapping exists)
-  - For OpenSearch: `registerRolloverLifecyclePolicy()` (updates ISM template 
patterns)
-  - Optionally: `removeIndexTemplate()` for removed types
-- **Note**: This is one of the most complex updates because it requires:
-  - List comparison (added/removed items)
-  - Access to mappings to verify item types are valid
-  - Different handling for ElasticSearch vs OpenSearch (ISM policy update)
-
-#### For `numberOfReplicas` (non-rollover):
-- **Action**: Optionally update existing non-rollover indices
-- **Method**: Use index settings update API
-- **Note**: This is optional - only affects existing indices, not templates
-
-## Implementation Requirements
-
-### 1. Configuration Change Detection
-- Store previous configuration values (or use OSGi Configuration Admin to 
detect changes)
-- Compare old vs new values to determine what changed
-
-### 2. Infrastructure Update Methods
-- **Update index template**: The existing `internalCreateRolloverTemplate()` 
method can be reused - `putIndexTemplate`/`putTemplate` operations are 
idempotent and will update existing templates
-- **Re-register lifecycle policy**: Already exists 
(`registerRolloverLifecyclePolicy()`) - policy updates are also idempotent
-- **Update existing indices**: Method to update index settings (for 
`numberOfReplicas`) - requires separate index settings update API call
-
-### 3. Rollover Item Type Discovery
-- Need to know which item types use rollover (from `rolloverIndices`)
-- Need access to mappings for those item types
-
-### 4. Error Handling
-- Handle cases where templates/policies don't exist
-- Handle partial failures gracefully
-- Log warnings for properties that can't be updated on existing indices
-
-## Minimal Change Implementation Strategy
-
-### Option 1: Extend `updated()` Method (Recommended)
-1. Add property change detection
-2. For each changed infrastructure-affecting property:
-   - Determine affected components
-   - Call appropriate update methods
-3. Keep existing simple property updates for non-infrastructure properties
-
-**Pros:**
-- Minimal code changes
-- Reuses existing methods
-- Clear separation of concerns
-
-**Cons:**
-- `updated()` method becomes more complex
-- Need to track previous values
-
-### Option 2: Separate Update Handler
-1. Create `InfrastructureUpdateHandler` class
-2. `updated()` method delegates infrastructure updates to handler
-3. Handler manages change detection and updates
-
-**Pros:**
-- Better separation of concerns
-- Easier to test
-- More maintainable
-
-**Cons:**
-- More code changes
-- Additional class to maintain
-
-### Recommended Approach: Option 1 with Helper Methods
-
-1. **Add configuration tracking**: Store previous values in instance variables
-2. **Create helper methods**:
-   - `updateRolloverTemplatesForAllItemTypes()` - Updates all rollover 
templates
-   - `updateRolloverTemplate(String itemType)` - Updates single template
-   - `updateExistingRolloverIndices(String itemType, String setting, Object 
value)` - Updates existing indices (optional)
-3. **Extend `updated()` method**:
-   - Detect changes by comparing old vs new values
-   - For each changed property, call appropriate update methods
-   - Update stored previous values after successful updates
-
-## Code Structure Changes Required
-
-### ElasticSearchPersistenceServiceImpl
-```java
-// Add fields to track previous values
-private String previousIndexPrefix;
-private String previousRolloverIndexNumberOfShards;
-private List<String> previousRolloverIndices;
-// ... etc for all infrastructure-affecting properties
-
-@Override
-public void updated(Dictionary<String, ?> properties) {
-    // Existing simple property mappings
-    Map<String, ConfigurationUpdateHelper.PropertyMapping> propertyMappings = 
new HashMap<>();
-    // ... existing mappings ...
-    
-    // Detect infrastructure-affecting changes
-    String newIndexPrefix = (String) properties.get("indexPrefix");
-    if (newIndexPrefix != null && !newIndexPrefix.equals(previousIndexPrefix)) 
{
-        updateInfrastructureForIndexPrefixChange(newIndexPrefix);
-        previousIndexPrefix = newIndexPrefix;
-    }
-    
-    // Handle rolloverIndices change (most complex)
-    String newRolloverIndicesStr = (String) properties.get("rolloverIndices");
-    if (newRolloverIndicesStr != null) {
-        List<String> newRolloverIndices = 
StringUtils.isNotEmpty(newRolloverIndicesStr) 
-            ? Arrays.asList(newRolloverIndicesStr.split(",")) 
-            : null;
-        if (!Objects.equals(newRolloverIndices, previousRolloverIndices)) {
-            updateInfrastructureForRolloverIndicesChange(newRolloverIndices);
-            previousRolloverIndices = newRolloverIndices;
-        }
-    }
-    
-    // Similar for other infrastructure properties...
-    
-    // Process simple property updates
-    ConfigurationUpdateHelper.processConfigurationUpdates(properties, LOGGER, 
"ElasticSearch persistence", propertyMappings);
-}
-
-private void updateInfrastructureForIndexPrefixChange(String newIndexPrefix) {
-    // Re-register lifecycle policy
-    registerRolloverLifecyclePolicy();
-    
-    // Recreate all rollover templates
-    if (rolloverIndices != null) {
-        for (String itemType : rolloverIndices) {
-            if (mappings.containsKey(itemType)) {
-                internalCreateRolloverTemplate(itemType);
-            }
-        }
-    }
-}
-
-private void updateRolloverTemplatesForSettingsChange() {
-    if (rolloverIndices != null) {
-        for (String itemType : rolloverIndices) {
-            if (mappings.containsKey(itemType)) {
-                updateRolloverTemplate(itemType);
-            }
-        }
-    }
-}
-
-private void updateInfrastructureForRolloverIndicesChange(List<String> 
newRolloverIndices) {
-    // Find added and removed item types
-    Set<String> previousSet = previousRolloverIndices != null 
-        ? new HashSet<>(previousRolloverIndices) 
-        : Collections.emptySet();
-    Set<String> newSet = newRolloverIndices != null 
-        ? new HashSet<>(newRolloverIndices) 
-        : Collections.emptySet();
-    
-    // Find newly added item types
-    Set<String> added = new HashSet<>(newSet);
-    added.removeAll(previousSet);
-    
-    // Find removed item types
-    Set<String> removed = new HashSet<>(previousSet);
-    removed.removeAll(newSet);
-    
-    // Create templates for newly added item types
-    for (String itemType : added) {
-        if (mappings.containsKey(itemType)) {
-            LOGGER.info("Creating rollover template for newly added item type: 
{}", itemType);
-            internalCreateRolloverTemplate(itemType);
-        } else {
-            LOGGER.warn("Cannot create rollover template for item type {}: 
mapping not found", itemType);
-        }
-    }
-    
-    // Optionally remove templates for removed item types
-    // (Keeping them doesn't cause issues, so this is optional)
-    for (String itemType : removed) {
-        String templateName = buildRolloverAlias(itemType) + 
"-rollover-template";
-        LOGGER.info("Removing rollover template for item type no longer in 
rollover list: {}", itemType);
-        // Note: This would require a removeIndexTemplate method or similar
-    }
-    
-    // For OpenSearch, also need to update ISM policy template patterns
-    // This is handled in OpenSearchPersistenceServiceImpl
-}
-```
-
-### OpenSearchPersistenceServiceImpl
-Similar structure, but with OpenSearch-specific API calls.
-
-## Testing Considerations
-
-1. **Unit Tests**: Test change detection logic
-2. **Integration Tests**: Test infrastructure updates with real ES/OS clusters
-3. **Edge Cases**:
-   - Template doesn't exist
-   - Policy doesn't exist
-   - Multiple properties change simultaneously
-   - Rollover indices list changes
-
-## Migration Notes
-
-- Existing deployments: First update will detect all properties as "changed" 
(no previous values)
-- Consider adding a flag to skip infrastructure updates on first update (only 
update in-memory values)
-- Or, always perform infrastructure updates (idempotent operations should be 
safe)
-
-## Summary
-
-The current `updated()` method is incomplete for infrastructure-affecting 
configuration properties. A minimal-change solution would:
-
-1. Track previous configuration values
-2. Detect changes in infrastructure-affecting properties
-3. Update corresponding infrastructure components:
-   - Index templates (for rollover settings)
-   - Lifecycle policies (for rollover parameters)
-   - Existing indices (optionally, for `numberOfReplicas`)
-4. Maintain backward compatibility with existing simple property updates
-
-The most critical properties requiring infrastructure updates are:
-- `rolloverIndices` (affects which templates exist, requires list comparison 
and template creation/removal)
-- `indexPrefix` (affects policy names, template names, aliases)
-- Rollover settings (`rolloverIndexNumberOfShards`, 
`rolloverIndexNumberOfReplicas`, etc.)
-- Rollover policy parameters (`rolloverMaxSize`, `rolloverMaxAge`, 
`rolloverMaxDocs`)
-
-**Note**: The `rolloverIndices` property is particularly important because:
-- It requires comparing old and new lists to detect added/removed item types
-- Newly added item types need rollover templates created immediately
-- OpenSearch requires ISM policy template patterns to be updated
-- This is a common configuration change scenario (e.g., adding `profile` to 
rollover list)
-
diff --git a/itests/src/test/java/org/apache/unomi/itests/ProgressListener.java 
b/itests/src/test/java/org/apache/unomi/itests/ProgressListener.java
index 49987a480..c0a826317 100644
--- a/itests/src/test/java/org/apache/unomi/itests/ProgressListener.java
+++ b/itests/src/test/java/org/apache/unomi/itests/ProgressListener.java
@@ -21,6 +21,10 @@ import org.junit.runner.Result;
 import org.junit.runner.notification.Failure;
 import org.junit.runner.notification.RunListener;
 
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
 import java.util.PriorityQueue;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -125,6 +129,8 @@ public class ProgressListener extends RunListener {
     private long startTime = System.currentTimeMillis();
     /** Timestamp when the current individual test started */
     private long startTestTime = System.currentTimeMillis();
+    /** Formatter for human-readable timestamps */
+    private static final DateTimeFormatter TIMESTAMP_FORMATTER = 
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
 
     /**
      * Creates a new ProgressListener instance.
@@ -164,6 +170,28 @@ public class ProgressListener extends RunListener {
         return text;
     }
 
+    /**
+     * Generates a separator bar of the specified length using the separator 
character.
+     *
+     * @param length the desired length of the separator bar
+     * @return a string of separator characters of the specified length
+     */
+    private String generateSeparator(int length) {
+        return "━".repeat(Math.max(1, length));
+    }
+
+    /**
+     * Calculates the visual length of a string, excluding ANSI escape codes.
+     *
+     * @param text the text to measure
+     * @return the visual length of the text without ANSI codes
+     */
+    private int getVisualLength(String text) {
+        // Remove ANSI escape sequences (pattern: ESC[ ... m)
+        String withoutAnsi = text.replaceAll("\u001B\\[[0-9;]*m", "");
+        return withoutAnsi.length();
+    }
+
     /**
      * Called when the test run starts. Displays an ASCII art logo and welcome 
message.
      *
@@ -228,10 +256,13 @@ public class ProgressListener extends RunListener {
         startTestTime = System.currentTimeMillis();
         // Print test start boundary with test name
         String testName = extractTestName(description);
+        String timestamp = formatTimestamp(startTestTime);
+        String message = "▶ START: " + testName + " [" + timestamp + "]";
+        String separator = generateSeparator(message.length());
         System.out.println(); // Blank line before test
-        
System.out.println(colorize("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
 CYAN));
-        System.out.println(colorize("▶ START: " + testName, GREEN));
-        
System.out.println(colorize("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
 CYAN));
+        System.out.println(colorize(separator, CYAN));
+        System.out.println(colorize(message, GREEN));
+        System.out.println(colorize(separator, CYAN));
     }
 
     /**
@@ -241,7 +272,8 @@ public class ProgressListener extends RunListener {
      */
     @Override
     public void testFinished(Description description) {
-        long testDuration = System.currentTimeMillis() - startTestTime;
+        long endTestTime = System.currentTimeMillis();
+        long testDuration = endTestTime - startTestTime;
         completedTests.incrementAndGet();
         successfulTests.incrementAndGet(); // Default to success unless a 
failure is recorded separately.
         slowTests.add(new TestTime(description.getDisplayName(), 
testDuration));
@@ -252,9 +284,12 @@ public class ProgressListener extends RunListener {
         // Print test end boundary
         String testName = extractTestName(description);
         String durationStr = formatTime(testDuration);
-        
System.out.println(colorize("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
 CYAN));
-        System.out.println(colorize("✓ END: " + testName + " (Duration: " + 
durationStr + ")", GREEN));
-        
System.out.println(colorize("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
 CYAN));
+        String timestamp = formatTimestamp(endTestTime);
+        String message = "✓ END: " + testName + " [" + timestamp + "] 
(Duration: " + durationStr + ")";
+        String separator = generateSeparator(message.length());
+        System.out.println(colorize(separator, CYAN));
+        System.out.println(colorize(message, GREEN));
+        System.out.println(colorize(separator, CYAN));
         System.out.println(); // Blank line before progress bar
         displayProgress();
         System.out.println(); // Blank line after progress bar
@@ -405,9 +440,21 @@ public class ProgressListener extends RunListener {
         String progressBar = generateProgressBar(((double) completed / 
totalTests) * 100);
         String humanReadableTime = formatTime(estimatedRemainingTime);
 
-        // Add visual separator and make progress bar more prominent
-        String separator = 
colorize("════════════════════════════════════════════════════════════════════════════════",
 CYAN);
-        System.out.println(separator);
+        // Build the plain message string (without ANSI codes) to calculate 
its length
+        String progressBarPlain = progressBar.replaceAll("\u001B\\[[0-9;]*m", 
"");
+        String plainMessage = String.format("[%s] Progress: %.2f%% (%d/%d 
tests). Estimated time remaining: %s. " +
+                        "Successful: %d, Failed: %d",
+                progressBarPlain,
+                ((double) completed / totalTests) * 100,
+                completed,
+                totalTests,
+                humanReadableTime,
+                successfulTests.get(),
+                failedTests.get());
+        
+        // Generate separator to match message length
+        String separator = generateSeparator(plainMessage.length());
+        System.out.println(colorize(separator, CYAN));
         System.out.printf("%s[%s]%s %sProgress: %s%.2f%%%s (%d/%d tests). 
Estimated time remaining: %s%s%s. " +
                         "Successful: %s%d%s, Failed: %s%d%s%n",
                 ansiSupported ? CYAN : "",
@@ -428,7 +475,7 @@ public class ProgressListener extends RunListener {
                 ansiSupported ? RED : "",
                 failedTests.get(),
                 ansiSupported ? RESET : "");
-        System.out.println(separator);
+        System.out.println(colorize(separator, CYAN));
 
         if (completed % Math.max(1, totalTests / 10) == 0 && completed < 
totalTests) {
             String quote = QUOTES[completed % QUOTES.length];
@@ -436,6 +483,17 @@ public class ProgressListener extends RunListener {
         }
     }
 
+    /**
+     * Formats a timestamp in milliseconds into a human-readable date-time 
string.
+     *
+     * @param timeInMillis the timestamp in milliseconds since epoch
+     * @return a formatted timestamp string (e.g., "2024-01-15 14:30:45")
+     */
+    private String formatTimestamp(long timeInMillis) {
+        return LocalDateTime.ofInstant(Instant.ofEpochMilli(timeInMillis), 
ZoneId.systemDefault())
+                .format(TIMESTAMP_FORMATTER);
+    }
+
     /**
      * Formats a time duration in milliseconds into a human-readable string.
      *
diff --git a/services-common/pom.xml b/services-common/pom.xml
index d4d77e89b..8344e9bee 100644
--- a/services-common/pom.xml
+++ b/services-common/pom.xml
@@ -139,6 +139,16 @@
                     </instructions>
                 </configuration>
             </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                    <systemPropertyVariables>
+                        <!-- Suppress stack traces in unit tests for expected 
errors to reduce log noise -->
+                        
<org.apache.unomi.ipvalidation.suppress.stacktraces>true</org.apache.unomi.ipvalidation.suppress.stacktraces>
+                    </systemPropertyVariables>
+                </configuration>
+            </plugin>
         </plugins>
     </build>
 
diff --git 
a/services/src/main/java/org/apache/unomi/services/impl/scheduler/SchedulerServiceImpl.java
 
b/services/src/main/java/org/apache/unomi/services/impl/scheduler/SchedulerServiceImpl.java
index d11167eb9..8bcddf22e 100644
--- 
a/services/src/main/java/org/apache/unomi/services/impl/scheduler/SchedulerServiceImpl.java
+++ 
b/services/src/main/java/org/apache/unomi/services/impl/scheduler/SchedulerServiceImpl.java
@@ -316,7 +316,7 @@ public class SchedulerServiceImpl implements 
SchedulerService {
                 return; // Services not ready yet
             }
 
-            LOGGER.info("Processing {} pending operations", 
pendingOperations.size());
+            LOGGER.debug("Processing {} pending operations", 
pendingOperations.size());
             int processedCount = 0;
             int errorCount = 0;
             int skippedCount = 0;
@@ -374,7 +374,7 @@ public class SchedulerServiceImpl implements 
SchedulerService {
             }
 
             if (processedCount > 0 || errorCount > 0 || skippedCount > 0) {
-                LOGGER.info("Processed {} pending operations ({} successful, 
{} errors, {} skipped due to missing persistence)",
+                LOGGER.debug("Processed {} pending operations ({} successful, 
{} errors, {} skipped due to missing persistence)",
                     processedCount + errorCount + skippedCount, 
processedCount, errorCount, skippedCount);
             }
         } finally {
@@ -602,7 +602,7 @@ public class SchedulerServiceImpl implements 
SchedulerService {
 
         // Process any pending operations that were waiting for the 
persistence provider
         if (servicesInitialized.get() && !pendingOperations.isEmpty()) {
-            LOGGER.info("Processing {} pending operations that were waiting 
for persistence provider", pendingOperations.size());
+            LOGGER.debug("Processing {} pending operations that were waiting 
for persistence provider", pendingOperations.size());
             processPendingOperations();
         }
     }
@@ -674,13 +674,13 @@ public class SchedulerServiceImpl implements 
SchedulerService {
 
         int clearedCount = originalSize - validOperations.size();
         if (clearedCount > 0) {
-            LOGGER.info("Cleared {} expired operations from pending queue", 
clearedCount);
+            LOGGER.debug("Cleared {} expired operations from pending queue", 
clearedCount);
         }
     }
 
     public void unsetPersistenceProvider(SchedulerProvider 
persistenceProvider) {
         this.persistenceProvider = null;
-        LOGGER.info("PersistenceSchedulerProvider unbound from 
SchedulerService");
+        LOGGER.debug("PersistenceSchedulerProvider unbound from 
SchedulerService");
     }
 
     /**
@@ -753,13 +753,13 @@ public class SchedulerServiceImpl implements 
SchedulerService {
         shutdownNow = true; // Set shutdown flag before other operations
         running.set(false);
 
-        LOGGER.info("SchedulerService preDestroy: beginning shutdown process");
+        LOGGER.debug("SchedulerService preDestroy: beginning shutdown 
process");
 
         // Clear pending operations queue
         int pendingCount = pendingOperations.size();
         if (pendingCount > 0) {
             pendingOperations.clear();
-            LOGGER.info("Cleared {} pending operations during shutdown", 
pendingCount);
+            LOGGER.debug("Cleared {} pending operations during shutdown", 
pendingCount);
         }
 
         // Notify all managers about shutdown
@@ -810,7 +810,7 @@ public class SchedulerServiceImpl implements 
SchedulerService {
             LOGGER.debug("Error clearing task collections: {}", 
e.getMessage());
         }
 
-        LOGGER.info("SchedulerService shutdown completed");
+        LOGGER.debug("SchedulerService shutdown completed");
     }
 
     /**
@@ -1450,7 +1450,7 @@ public class SchedulerServiceImpl implements 
SchedulerService {
      */
     private void initializeTaskPurgeInternal() {
         if (!purgeTaskEnabled) {
-            LOGGER.info("Task purge is disabled, skipping initialization");
+            LOGGER.debug("Task purge is disabled, skipping initialization");
             return;
         }
 
@@ -1471,12 +1471,12 @@ public class SchedulerServiceImpl implements 
SchedulerService {
 
             @Override
             public void execute(ScheduledTask task, TaskStatusCallback 
callback) {
-                LOGGER.info("Purge task executor called - starting purge of 
old tasks");
+                LOGGER.debug("Purge task executor called - starting purge of 
old tasks");
                 try {
                     if (persistenceProvider != null) {
-                        LOGGER.info("Calling 
persistenceProvider.purgeOldTasks() with TTL: {} days", completedTaskTtlDays);
+                        LOGGER.debug("Calling 
persistenceProvider.purgeOldTasks() with TTL: {} days", completedTaskTtlDays);
                         persistenceProvider.purgeOldTasks();
-                        LOGGER.info("Purge task completed successfully");
+                        LOGGER.debug("Purge task completed successfully");
                     } else {
                         LOGGER.warn("Persistence provider is null, cannot 
purge tasks");
                     }
@@ -1489,7 +1489,7 @@ public class SchedulerServiceImpl implements 
SchedulerService {
         };
 
         registerTaskExecutor(taskPurgeExecutor);
-        LOGGER.info("Registered purge task executor");
+        LOGGER.debug("Registered purge task executor");
 
         // Check if a task purge task already exists
         List<ScheduledTask> existingTasks = getTasksByType("task-purge", 0, 1, 
null).getList();
@@ -1504,7 +1504,7 @@ public class SchedulerServiceImpl implements 
SchedulerService {
             taskPurgeTask.setFixedRate(true);
             taskPurgeTask.setEnabled(true);
             saveTask(taskPurgeTask);
-            LOGGER.info("Reusing existing system task purge task: {}", 
taskPurgeTask.getItemId());
+            LOGGER.debug("Reusing existing system task purge task: {}", 
taskPurgeTask.getItemId());
         } else {
             // Create a new task if none exists or existing one isn't a system 
task
             taskPurgeTask = newTask("task-purge")
@@ -1512,7 +1512,7 @@ public class SchedulerServiceImpl implements 
SchedulerService {
                 .withFixedRate()
                 .asSystemTask()
                 .schedule();
-            LOGGER.info("Created new system task purge task: {}", 
taskPurgeTask.getItemId());
+            LOGGER.debug("Created new system task purge task: {}", 
taskPurgeTask.getItemId());
         }
     }
 
@@ -1995,7 +1995,7 @@ public class SchedulerServiceImpl implements 
SchedulerService {
                 if (!existingTasks.isEmpty() && 
existingTasks.get(0).isSystemTask()) {
                     // Reuse the existing system task
                     ScheduledTask existingTask = existingTasks.get(0);
-                    LOGGER.info("Reusing existing system task: {}", 
existingTask.getItemId());
+                    LOGGER.debug("Reusing existing system task: {}", 
existingTask.getItemId());
 
                     // Schedule the existing task
                     schedulerService.scheduleTask(existingTask);
diff --git 
a/services/src/main/java/org/apache/unomi/services/impl/scheduler/TaskExecutionManager.java
 
b/services/src/main/java/org/apache/unomi/services/impl/scheduler/TaskExecutionManager.java
index a608d55ca..bff78e02e 100644
--- 
a/services/src/main/java/org/apache/unomi/services/impl/scheduler/TaskExecutionManager.java
+++ 
b/services/src/main/java/org/apache/unomi/services/impl/scheduler/TaskExecutionManager.java
@@ -18,12 +18,12 @@ package org.apache.unomi.services.impl.scheduler;
 
 import org.apache.unomi.api.tasks.ScheduledTask;
 import org.apache.unomi.api.tasks.TaskExecutor;
-import org.apache.unomi.api.services.SchedulerService;
-import org.apache.unomi.persistence.spi.PersistenceService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.util.*;
+import java.util.Date;
+import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.*;
 import java.util.concurrent.atomic.AtomicBoolean;
 
@@ -115,7 +115,7 @@ public class TaskExecutionManager {
                 TASK_CHECK_INTERVAL,
                 TimeUnit.MILLISECONDS
             );
-            LOGGER.info("Task checker started with interval {} ms", 
TASK_CHECK_INTERVAL);
+            LOGGER.debug("Task checker started with interval {} ms", 
TASK_CHECK_INTERVAL);
         }
     }
 
@@ -126,7 +126,7 @@ public class TaskExecutionManager {
         if (running.compareAndSet(true, false) && taskCheckerFuture != null) {
             taskCheckerFuture.cancel(false);
             taskCheckerFuture = null;
-            LOGGER.info("Task checker stopped");
+            LOGGER.debug("Task checker stopped");
         }
     }
 
@@ -263,7 +263,7 @@ public class TaskExecutionManager {
                 LOGGER.debug("Node {} : Skipping task {} execution as 
scheduler is shutting down", nodeId, task != null ? task.getItemId() : 
"unknown");
                 return;
             }
-            
+
             if (task == null) {
                 LOGGER.error("Node {} : Cannot execute null task", nodeId);
                 return;
@@ -291,7 +291,7 @@ public class TaskExecutionManager {
             if (!prepareForExecution(task)) {
                 return;
             }
-            
+
             // Final shutdown check before executing
             if (schedulerService != null && schedulerService.isShutdownNow()) {
                 LOGGER.debug("Node {} : Skipping task {} execution as 
scheduler is shutting down", nodeId, taskId);
diff --git 
a/services/src/test/java/org/apache/unomi/services/impl/scheduler/SchedulerServiceImplTest.java
 
b/services/src/test/java/org/apache/unomi/services/impl/scheduler/SchedulerServiceImplTest.java
index a1fbd0531..18afaf028 100644
--- 
a/services/src/test/java/org/apache/unomi/services/impl/scheduler/SchedulerServiceImplTest.java
+++ 
b/services/src/test/java/org/apache/unomi/services/impl/scheduler/SchedulerServiceImplTest.java
@@ -17,6 +17,7 @@
 package org.apache.unomi.services.impl.scheduler;
 
 import org.apache.unomi.api.PartialList;
+import org.apache.unomi.api.services.SchedulerService.TaskBuilder;
 import org.apache.unomi.api.tasks.ScheduledTask;
 import org.apache.unomi.api.tasks.TaskExecutor;
 import org.apache.unomi.persistence.spi.CustomObjectMapper;
@@ -24,15 +25,14 @@ import org.apache.unomi.persistence.spi.PersistenceService;
 import 
org.apache.unomi.persistence.spi.conditions.evaluator.ConditionEvaluatorDispatcher;
 import org.apache.unomi.services.TestHelper;
 import org.apache.unomi.services.common.security.ExecutionContextManagerImpl;
-import org.apache.unomi.services.impl.InMemoryPersistenceServiceImpl;
 import org.apache.unomi.services.common.security.KarafSecurityService;
+import org.apache.unomi.services.impl.InMemoryPersistenceServiceImpl;
 import org.apache.unomi.services.impl.TestConditionEvaluators;
 import org.apache.unomi.services.impl.cluster.ClusterServiceImpl;
-import org.apache.unomi.api.services.SchedulerService.TaskBuilder;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.junit.jupiter.api.*;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
@@ -40,10 +40,14 @@ import org.mockito.junit.jupiter.MockitoSettings;
 import org.mockito.quality.Strictness;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
 import java.util.*;
-import java.util.concurrent.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicLong;
@@ -76,7 +80,7 @@ import static org.mockito.Mockito.when;
 public class SchedulerServiceImplTest {
     private static final Logger LOGGER = 
LoggerFactory.getLogger(SchedulerServiceImplTest.class);
 
-    
+
 
     // Test configuration constants
     /** Maximum number of retries for storage operations */
@@ -206,7 +210,7 @@ public class SchedulerServiceImplTest {
         assertTrue(executionLatch.await(TEST_TIMEOUT, TEST_TIME_UNIT), "Task 
should execute");
         assertTrue(executed.get(), "Task should have executed");
 
-        ScheduledTask completedTask = 
schedulerService.getTask(task.getItemId());
+        ScheduledTask completedTask = waitForTaskStatus(task.getItemId(), 
ScheduledTask.TaskStatus.COMPLETED, TEST_WAIT_TIMEOUT, 50);
         assertEquals(ScheduledTask.TaskStatus.COMPLETED, 
completedTask.getStatus(), "Task should be completed");
         
assertNotNull(completedTask.getStatusDetails().get("executionHistory"), "Task 
should have execution history");
     }
@@ -1541,7 +1545,7 @@ public class SchedulerServiceImplTest {
                 long extendedTimeout = Math.max(TEST_TIMEOUT, 3000); // At 
least 3 seconds
                 node2Executed = node2Latch.await(extendedTimeout, 
TEST_TIME_UNIT);
             }
-            
+
             assertTrue(
                 node2Executed,
                 "Task should execute on node2 within timeout after discovery 
(allowing for task period and checker cycles)");
diff --git a/tracing/tracing-impl/pom.xml b/tracing/tracing-impl/pom.xml
index 2b4a487e2..696a1413c 100644
--- a/tracing/tracing-impl/pom.xml
+++ b/tracing/tracing-impl/pom.xml
@@ -68,8 +68,8 @@
             <scope>provided</scope>
         </dependency>
         <dependency>
-            <groupId>junit</groupId>
-            <artifactId>junit</artifactId>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter</artifactId>
             <scope>test</scope>
         </dependency>
     </dependencies>


Reply via email to