Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions ProcessMaker/Managers/TaskSchedulerManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@

class TaskSchedulerManager implements JobManagerInterface, EventBusInterface
{
private static $today = null;
private $today = null;

protected $registerStartEvents = false;

Expand Down Expand Up @@ -814,7 +814,9 @@ private function nextDateForDuration(DateTimeInterface $currentDate, $jsonInterv
*/
public function today()
{
return self::$today ?: (Carbon::now())->setTimezone(new DateTimeZone('UTC'));
// Use createFromTimestamp to get the real current time,
// avoiding Carbon::setTestNow() global state (Octane data leak).
return $this->today ?: Carbon::createFromTimestamp(time(), new DateTimeZone('UTC'));
}

/**
Expand All @@ -824,18 +826,18 @@ public function today()
*
* @return DateTime
*/
public static function fakeToday($today)
public function fakeToday($today)
{
if ($today === null) {
Carbon::setTestNow(null);

return self::$today = $today;
return $this->today = $today;
}
$fake = $today instanceof DateTime ? clone $today : (new DateTime($today))->setTimezone(new DateTimeZone('UTC'));
self::$today = new Carbon($fake->format('c'));
$this->today = new Carbon($fake->format('c'));
Carbon::setTestNow($fake->format('c'));

return clone self::$today;
return clone $this->today;
}

/**
Expand Down
24 changes: 12 additions & 12 deletions tests/Feature/Api/BoundaryEventsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public function testSignalBoundaryEvent()
public function testCycleTimerBoundaryEvent()
{
// Mock current date for TaskSchedulerManager
$now = TaskSchedulerManager::fakeToday('2018-05-01T00:00:00Z');
$now = (new TaskSchedulerManager())->fakeToday('2018-05-01T00:00:00Z');

// Create a process
$process = $this->createProcess(file_get_contents(__DIR__ . '/processes/Timer_BoundaryEvent_Cycle.bpmn'));
Expand All @@ -71,7 +71,7 @@ public function testCycleTimerBoundaryEvent()

// Trigger timer event
$now->modify('+1 minute');
TaskSchedulerManager::fakeToday($now);
(new TaskSchedulerManager())->fakeToday($now);
$this->runScheduledTasks();

// There should be no scheduled tasks
Expand Down Expand Up @@ -138,7 +138,7 @@ public function testErrorBoundaryEventCallActivity()
public function testCycleTimerBoundaryEventCallActivity()
{
// Mock current date for TaskSchedulerManager
$now = TaskSchedulerManager::fakeToday('2018-05-01T00:00:00Z');
$now = (new TaskSchedulerManager())->fakeToday('2018-05-01T00:00:00Z');

// Create a process
$process = $this->createProcess(file_get_contents(__DIR__ . '/processes/Timer_BoundaryEvent_CallActivity.bpmn'));
Expand All @@ -163,7 +163,7 @@ public function testCycleTimerBoundaryEventCallActivity()

// Trigger timer event
$now->modify('+1 minute');
TaskSchedulerManager::fakeToday($now);
(new TaskSchedulerManager())->fakeToday($now);
$this->runScheduledTasks();

// Get active tokens
Expand Down Expand Up @@ -207,7 +207,7 @@ public function testSignalBoundaryEventCallActivity()
public function testCycleTimerBoundaryEventNonInterrupting()
{
// Mock current date for TaskSchedulerManager
$now = TaskSchedulerManager::fakeToday('2018-05-01T00:00:00Z');
$now = (new TaskSchedulerManager())->fakeToday('2018-05-01T00:00:00Z');

// Create a process
$process = $this->createProcess(file_get_contents(__DIR__ . '/processes/Timer_BoundaryEvent_Cycle_NonInterrupting.bpmn'));
Expand All @@ -224,7 +224,7 @@ public function testCycleTimerBoundaryEventNonInterrupting()

// Trigger timer event
$now->modify('+1 minute');
TaskSchedulerManager::fakeToday($now);
(new TaskSchedulerManager())->fakeToday($now);
$this->runScheduledTasks();

// Get active tokens
Expand Down Expand Up @@ -290,7 +290,7 @@ public function testErrorBoundaryEventCallActivityNonInterrupting()
public function testCycleTimerBoundaryEventCallActivityNonInterrupting()
{
// Mock current date for TaskSchedulerManager
$now = TaskSchedulerManager::fakeToday('2018-05-01T00:00:00Z');
$now = (new TaskSchedulerManager())->fakeToday('2018-05-01T00:00:00Z');

// Create a process
$process = $this->createProcess(file_get_contents(__DIR__ . '/processes/Timer_BoundaryEvent_CallActivity_NonInterrupting.bpmn'));
Expand All @@ -315,7 +315,7 @@ public function testCycleTimerBoundaryEventCallActivityNonInterrupting()

// Trigger timer event
$now->modify('+1 minute');
TaskSchedulerManager::fakeToday($now);
(new TaskSchedulerManager())->fakeToday($now);
$this->runScheduledTasks();

// Get active tokens
Expand Down Expand Up @@ -359,7 +359,7 @@ public function testSignalBoundaryEventCallActivityNonInterrupting()
public function testConcurrentBoundaryEventCallActivityNonInterrupting()
{
// Mock current date for TaskSchedulerManager
$now = TaskSchedulerManager::fakeToday('2018-05-01T00:00:00Z');
$now = (new TaskSchedulerManager())->fakeToday('2018-05-01T00:00:00Z');

// Create a process
$process = $this->createProcess(file_get_contents(__DIR__ . '/processes/Concurrent_BoundaryEvent_CallActivity_NonInterrupting.bpmn'));
Expand All @@ -376,7 +376,7 @@ public function testConcurrentBoundaryEventCallActivityNonInterrupting()

// Trigger timer event
$now->modify('+4 minute');
TaskSchedulerManager::fakeToday($now);
(new TaskSchedulerManager())->fakeToday($now);
$this->runScheduledTasks();

// Get active tokens
Expand All @@ -401,7 +401,7 @@ public function testConcurrentBoundaryEventCallActivityNonInterrupting()
*/
public function testTimerBoundaryEventMultiInstance()
{
$now = TaskSchedulerManager::fakeToday('2018-05-01T00:00:00Z');
$now = (new TaskSchedulerManager())->fakeToday('2018-05-01T00:00:00Z');
// Create a process
$process = $this->createProcess(file_get_contents(__DIR__ . '/processes/Timer_BoundaryEvent_MultiInstance.bpmn'));

Expand All @@ -426,7 +426,7 @@ public function testTimerBoundaryEventMultiInstance()

// Move forward 1 minute
$now->modify('+1 minute');
TaskSchedulerManager::fakeToday($now);
(new TaskSchedulerManager())->fakeToday($now);
$this->runScheduledTasks();

// BoundaryEvent catches timer then
Expand Down
12 changes: 6 additions & 6 deletions tests/Feature/Api/IntermediateTimerEventTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -110,14 +110,14 @@ public function testScheduleIntermediateTimerEvent()
public function testConnectedTimerEvents()
{
// Mock current date for TaskSchedulerManager
$now = TaskSchedulerManager::fakeToday('2018-10-01T00:00:00Z');
$now = (new TaskSchedulerManager())->fakeToday('2018-10-01T00:00:00Z');

// Create a process
$process = $this->createProcess(file_get_contents(__DIR__ . '/processes/TimerEvents_Intermediate_Start.bpmn'));

$now->modify('+1 day');
//$now->modify('+1 minute');
TaskSchedulerManager::fakeToday($now);
(new TaskSchedulerManager())->fakeToday($now);
$this->runScheduledTasks();

// One process request is started by the timer event
Expand All @@ -134,15 +134,15 @@ public function testConnectedTimerEvents()

// Increase 4 hours, nothing must change
$now->modify('+4 hour');
TaskSchedulerManager::fakeToday($now);
(new TaskSchedulerManager())->fakeToday($now);
$this->runScheduledTasks();
$activeTokens = $instance->tokens()->where('status', 'ACTIVE')->get();
$this->assertCount(1, $activeTokens);
$this->assertEquals('every day 8:00 UTC', $activeTokens[0]->element_name);

// Trigger first intermediate timer event
$now->modify('+4 hour');
TaskSchedulerManager::fakeToday($now);
(new TaskSchedulerManager())->fakeToday($now);
$this->runScheduledTasks();

// "every day 8:30 -4:00" must be active
Expand All @@ -153,7 +153,7 @@ public function testConnectedTimerEvents()
// Increase 4:30 hours, "every day 8:30 -4:00" is reached (12:30 UTC)
$now->modify('+4 hour');
$now->modify('+30 minute');
TaskSchedulerManager::fakeToday($now);
(new TaskSchedulerManager())->fakeToday($now);
$this->runScheduledTasks();

// "wait 4 hours" must be active (16:00)
Expand All @@ -163,7 +163,7 @@ public function testConnectedTimerEvents()

// Increase 4 hours
$now->modify('+4 hour');
TaskSchedulerManager::fakeToday($now);
(new TaskSchedulerManager())->fakeToday($now);
$this->runScheduledTasks();

// Process is completed
Expand Down
8 changes: 4 additions & 4 deletions tests/Feature/ScheduledTaskDuplicationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ public function testTaskNotExecutedIfNextDateInFuture()

// Set a fake "today" to control time precisely
$fakeToday = Carbon::create(2026, 1, 12, 12, 0, 0, 'UTC');
TaskSchedulerManager::fakeToday($fakeToday);
(new TaskSchedulerManager())->fakeToday($fakeToday);

// Create a task that was executed 5 minutes ago with 60-minute interval
// Next execution should be at 12:55 (55 minutes in the future)
Expand Down Expand Up @@ -317,7 +317,7 @@ public function testTaskNotExecutedIfNextDateInFuture()
$this->assertGreaterThan($today->getTimestamp(), $nextDate->getTimestamp());

// Reset fake today
TaskSchedulerManager::fakeToday(null);
(new TaskSchedulerManager())->fakeToday(null);
}

/**
Expand All @@ -329,7 +329,7 @@ public function testTaskExecutedIfNextDatePassed()

// Set a fake "today" to control time precisely
$fakeToday = Carbon::create(2026, 1, 12, 12, 30, 0, 'UTC');
TaskSchedulerManager::fakeToday($fakeToday);
(new TaskSchedulerManager())->fakeToday($fakeToday);

// Create a task with last execution at 12:00, 1-minute interval
// Next execution should be at 12:01, which is 29 minutes in the past
Expand Down Expand Up @@ -360,7 +360,7 @@ public function testTaskExecutedIfNextDatePassed()
$this->assertLessThanOrEqual($today->getTimestamp(), $nextDate->getTimestamp());

// Reset fake today
TaskSchedulerManager::fakeToday(null);
(new TaskSchedulerManager())->fakeToday(null);
}

/**
Expand Down
3 changes: 2 additions & 1 deletion tests/Feature/Shared/ProcessTestingTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ private function runScheduledTasks()
*/
protected function teardownProcessTestingTrait()
{
TaskSchedulerManager::fakeToday(null);
$manager = new TaskSchedulerManager();
$manager->fakeToday(null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<?php

namespace Tests\Unit\ProcessMaker\Managers;

use Carbon\Carbon;
use ProcessMaker\Managers\TaskSchedulerManager;
use Tests\TestCase;

class TaskSchedulerManagerOctaneTest extends TestCase
{
/**
* Test that fakeToday on one instance does not leak to another instance.
* This is the Octane data leak scenario: static properties persist across requests.
*/
public function testFakeTodayDoesNotLeakBetweenInstances()
{
// Instance 1: set a fake date
$manager1 = new TaskSchedulerManager();
$manager1->fakeToday('2020-01-15T10:30:00Z');

// Instance 2: should NOT see the fake date from instance 1
$manager2 = new TaskSchedulerManager();
$today2 = $manager2->today();

// Instance 2 should return the real current date, not the fake one
$this->assertNotEquals('2020-01-15', $today2->format('Y-m-d'));

// Cleanup
$manager1->fakeToday(null);
}

/**
* Test that fakeToday persists within the same instance.
*/
public function testFakeTodayPersistsWithinSameInstance()
{
$manager = new TaskSchedulerManager();
$manager->fakeToday('2020-01-15T10:30:00Z');

$today = $manager->today();

$this->assertEquals('2020-01-15', $today->format('Y-m-d'));
$this->assertEquals('10:30:00', $today->format('H:i:s'));

$manager->fakeToday(null);
}

/**
* Test that fakeToday(null) resets the date to the real current date.
*/
public function testFakeTodayNullResetsToRealDate()
{
$manager = new TaskSchedulerManager();
$manager->fakeToday('2020-01-15T10:30:00Z');

// Reset
$manager->fakeToday(null);

$today = $manager->today();
$this->assertNotEquals('2020-01-15', $today->format('Y-m-d'));
}

/**
* Test that Carbon::setTestNow is also reset when fakeToday(null) is called.
*/
public function testCarbonTestNowIsReset()
{
$manager = new TaskSchedulerManager();
$manager->fakeToday('2020-01-15T10:30:00Z');

// Carbon::now() should return the fake date
$this->assertEquals('2020-01-15', Carbon::now()->format('Y-m-d'));

// Reset
$manager->fakeToday(null);

// Carbon::now() should return the real date
$this->assertNotEquals('2020-01-15', Carbon::now()->format('Y-m-d'));
}

/**
* Test that multiple instances can have different fake dates simultaneously.
* This simulates concurrent requests in Octane.
*/
public function testMultipleInstancesCanHaveDifferentFakeDates()
{
$manager1 = new TaskSchedulerManager();
$manager1->fakeToday('2020-01-15T10:30:00Z');

$manager2 = new TaskSchedulerManager();
$manager2->fakeToday('2021-06-20T08:00:00Z');

$this->assertEquals('2020-01-15', $manager1->today()->format('Y-m-d'));
$this->assertEquals('2021-06-20', $manager2->today()->format('Y-m-d'));

$manager1->fakeToday(null);
$manager2->fakeToday(null);
}
}
Loading