From e5095eb1332f6217a07b610f07f95d8cafd26ab2 Mon Sep 17 00:00:00 2001 From: TienNM99 Date: Sun, 10 Sep 2023 15:53:23 +0700 Subject: [PATCH] [Scheduler Pattern] (Add) scheduler pattern --- pom.xml | 1 + scheduler/README.md | 54 ++++++++++++++++++ scheduler/etc/scheduler.png | Bin 0 -> 62628 bytes scheduler/etc/scheduler.puml | 41 +++++++++++++ scheduler/pom.xml | 28 +++++++++ .../FirstComeFirstServedScheduler.java | 35 ++++++++++++ .../iluwatar/scheduler/PriorityScheduler.java | 41 +++++++++++++ .../scheduler/RoundRobinScheduler.java | 21 +++++++ .../ShortestRemainingTimeFirstScheduler.java | 28 +++++++++ .../com/iluwatar/scheduler/Simulator.java | 52 +++++++++++++++++ .../java/com/iluwatar/scheduler/Task.java | 44 ++++++++++++++ .../com/iluwatar/scheduler/TaskScheduler.java | 10 ++++ .../FirstComeFirstServedSchedulerTest.java | 49 ++++++++++++++++ .../scheduler/PrioritySchedulerTest.java | 49 ++++++++++++++++ .../scheduler/RoundRobinSchedulerTest.java | 45 +++++++++++++++ ...ortestRemainingTimeFirstSchedulerTest.java | 45 +++++++++++++++ 16 files changed, 543 insertions(+) create mode 100644 scheduler/README.md create mode 100644 scheduler/etc/scheduler.png create mode 100644 scheduler/etc/scheduler.puml create mode 100644 scheduler/pom.xml create mode 100644 scheduler/src/main/java/com/iluwatar/scheduler/FirstComeFirstServedScheduler.java create mode 100644 scheduler/src/main/java/com/iluwatar/scheduler/PriorityScheduler.java create mode 100644 scheduler/src/main/java/com/iluwatar/scheduler/RoundRobinScheduler.java create mode 100644 scheduler/src/main/java/com/iluwatar/scheduler/ShortestRemainingTimeFirstScheduler.java create mode 100644 scheduler/src/main/java/com/iluwatar/scheduler/Simulator.java create mode 100644 scheduler/src/main/java/com/iluwatar/scheduler/Task.java create mode 100644 scheduler/src/main/java/com/iluwatar/scheduler/TaskScheduler.java create mode 100644 scheduler/src/test/java/com/iluwatar/scheduler/FirstComeFirstServedSchedulerTest.java create mode 100644 scheduler/src/test/java/com/iluwatar/scheduler/PrioritySchedulerTest.java create mode 100644 scheduler/src/test/java/com/iluwatar/scheduler/RoundRobinSchedulerTest.java create mode 100644 scheduler/src/test/java/com/iluwatar/scheduler/ShortestRemainingTimeFirstSchedulerTest.java diff --git a/pom.xml b/pom.xml index 2bf8874fa7d..0965eddecca 100644 --- a/pom.xml +++ b/pom.xml @@ -207,6 +207,7 @@ context-object thread-local-storage optimistic-offline-lock + scheduler diff --git a/scheduler/README.md b/scheduler/README.md new file mode 100644 index 00000000000..04b38a8256d --- /dev/null +++ b/scheduler/README.md @@ -0,0 +1,54 @@ +--- +title: Scheduler Pattern +category: Creational +language: en +tag: +--- + +## Name +Scheduler Design Pattern + +## Intent +The Scheduler Design Pattern is used to manage and coordinate the execution of tasks or jobs in a system. It provides a mechanism for scheduling and executing tasks at specific times, intervals, or in response to certain events. This pattern is especially useful when dealing with asynchronous operations, background processing, and resource allocation. + +## Explanation +The Scheduler Design Pattern is designed to decouple task scheduling from the actual execution of tasks. It abstracts the scheduling logic, making it possible to change or extend the scheduling behavior without affecting the tasks themselves. This pattern allows for efficient resource utilization, load balancing, and prioritization of tasks. + +## Class diagram +![Scheduler Pattern](etc/scheduler.png) + +## Applicability +The Scheduler Design Pattern is applicable in various scenarios, including but not limited to: + +- **Task Queue Management**: When you need to manage a queue of tasks to be executed, ensuring tasks are executed in a specific order, on specific resources, or with certain priorities. + +- **Background Processing**: In applications requiring background jobs, such as processing user requests asynchronously, sending emails, or performing periodic maintenance tasks. + +- **Resource Allocation**: For managing shared resources, like database connections or thread pools, to ensure fair allocation among competing tasks. + +- **Real-time Systems**: In systems where tasks need to be executed at precise times or in response to specific events, such as in real-time simulations or monitoring systems. + +## Known uses +The Scheduler Design Pattern is used in various software applications and frameworks, including: + +- Operating systems for managing processes. +- Java: The Java `ScheduledExecutorService` class is an implementation of the Scheduler Design Pattern, allowing the scheduling of tasks at fixed rate or with fixed delay. + +## Consequences +The Scheduler Design Pattern offers several advantages: +- **Flexibility**: It allows for dynamic scheduling of tasks, making it adaptable to changing requirements. +- **Efficiency**: Tasks can be optimized for resource utilization, and parallel execution can be managed effectively. +- **Maintainability**: Separating scheduling logic from task execution simplifies maintenance and debugging. + +However, it also has some potential drawbacks: +- **Complexity**: Implementing a scheduler can be complex, especially in systems with intricate scheduling requirements. +- **Overhead**: Maintaining a scheduler adds some overhead to the system. + + +## Related patterns +The Scheduler Design Pattern is related to other design patterns, including: +- **Observer Pattern**: When tasks need to be scheduled in response to specific events or changes in the system, the Observer Pattern can be used in conjunction with the Scheduler Pattern. +- **Command Pattern**: Tasks to be executed by the scheduler can often be encapsulated using the Command Pattern, allowing for easy parameterization and queuing. + +## Credits +- [Wikipedia: Scheduling (computing)](https://en.wikipedia.org/wiki/Scheduling_(computing)) diff --git a/scheduler/etc/scheduler.png b/scheduler/etc/scheduler.png new file mode 100644 index 0000000000000000000000000000000000000000..1a0b36060d11ff21a55fa9fe353980ae30ca8a6c GIT binary patch literal 62628 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�Ag@u@K+({FfJ~`3~82x%~C(k6h)O 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 z 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

|w5O~}kggm5 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 literal 0 HcmV?d00001 diff --git a/scheduler/etc/scheduler.puml b/scheduler/etc/scheduler.puml new file mode 100644 index 00000000000..c6b3357bda6 --- /dev/null +++ b/scheduler/etc/scheduler.puml @@ -0,0 +1,41 @@ +@startuml + +class Task { + -id: int + -totalExecutionTime: int + -priority: int + -- + +Task(id: int, totalExecutionTime: int, priority: int) + +Task(id: int, totalExecutionTime: int) + +getId(): int + +getTotalExecutionTime(): int + +getPriority(): int +} + +interface TaskScheduler { + +scheduleTask(task: Task): void + +update(int deltaTime): void +} + +class FirstComeFirstServedScheduler extends TaskScheduler {} +class PriorityScheduler extends TaskScheduler {} +class RoundRobinScheduler extends TaskScheduler {} +class ShortestRemainingTimeFirstScheduler extends TaskScheduler {} + +class Simulator { + -scheduler: TaskScheduler + -Map> tasks + -deltaTime: int + -simulateTime: int + -LinkedHashMap taskCompletedOrder + -elapsedTime: int + -- + +Simulator(scheduler: TaskScheduler, tasks: Map>, deltaTime: int, simulateTime: int) + +simulate(): LinkedHashMap +} + +Task -- TaskScheduler : "1..*" +TaskScheduler -- Simulator : "1" +Simulator ..> Task : "1..*" + +@enduml diff --git a/scheduler/pom.xml b/scheduler/pom.xml new file mode 100644 index 00000000000..744f36ddc08 --- /dev/null +++ b/scheduler/pom.xml @@ -0,0 +1,28 @@ + + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.26.0-SNAPSHOT + + + scheduler + + + 17 + 17 + UTF-8 + + + + org.junit.jupiter + junit-jupiter-api + 5.9.2 + test + + + + diff --git a/scheduler/src/main/java/com/iluwatar/scheduler/FirstComeFirstServedScheduler.java b/scheduler/src/main/java/com/iluwatar/scheduler/FirstComeFirstServedScheduler.java new file mode 100644 index 00000000000..b7a1d9a285d --- /dev/null +++ b/scheduler/src/main/java/com/iluwatar/scheduler/FirstComeFirstServedScheduler.java @@ -0,0 +1,35 @@ +package com.iluwatar.scheduler; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.LinkedList; +import java.util.Queue; + +public class FirstComeFirstServedScheduler implements TaskScheduler, PropertyChangeListener { + private final Queue taskQueue = new LinkedList<>(); + + @Override + public void scheduleTask(Task task) { + task.getSupport().addPropertyChangeListener(this); + taskQueue.add(task); + } + + @Override + public void update(int deltaTime) { + Task task = taskQueue.peek(); + if (task == null) return; + task.execute(deltaTime); + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (Task.COMPLETE_PROPERTY.equals(evt.getPropertyName())) { + onTaskComplete(evt); + } + } + + private void onTaskComplete(PropertyChangeEvent evt) { + Task task = (Task) evt.getSource(); + taskQueue.remove(task); + } +} diff --git a/scheduler/src/main/java/com/iluwatar/scheduler/PriorityScheduler.java b/scheduler/src/main/java/com/iluwatar/scheduler/PriorityScheduler.java new file mode 100644 index 00000000000..6d084350b20 --- /dev/null +++ b/scheduler/src/main/java/com/iluwatar/scheduler/PriorityScheduler.java @@ -0,0 +1,41 @@ +package com.iluwatar.scheduler; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.PriorityQueue; +import java.util.Queue; + +public class PriorityScheduler implements TaskScheduler, PropertyChangeListener { + private final Queue taskQueue = + new PriorityQueue<>( + (task1, task2) -> { + if (task2.getPriority() != task1.getPriority()) + return task2.getPriority() - task1.getPriority(); + return task1.getId() - task2.getId(); // lower id (earlier task) has higher priority + }); + + @Override + public void scheduleTask(Task task) { + task.getSupport().addPropertyChangeListener(this); + taskQueue.add(task); + } + + @Override + public void update(int deltaTime) { + Task task = taskQueue.peek(); + if (task == null) return; + task.execute(deltaTime); + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (Task.COMPLETE_PROPERTY.equals(evt.getPropertyName())) { + onTaskComplete(evt); + } + } + + private void onTaskComplete(PropertyChangeEvent evt) { + Task task = (Task) evt.getSource(); + taskQueue.remove(task); + } +} diff --git a/scheduler/src/main/java/com/iluwatar/scheduler/RoundRobinScheduler.java b/scheduler/src/main/java/com/iluwatar/scheduler/RoundRobinScheduler.java new file mode 100644 index 00000000000..20cd819ca3a --- /dev/null +++ b/scheduler/src/main/java/com/iluwatar/scheduler/RoundRobinScheduler.java @@ -0,0 +1,21 @@ +package com.iluwatar.scheduler; + +import java.util.LinkedList; +import java.util.Queue; + +public class RoundRobinScheduler implements TaskScheduler { + private final Queue taskQueue = new LinkedList<>(); + + @Override + public void scheduleTask(Task task) { + taskQueue.add(task); + } + + @Override + public void update(int deltaTime) { + Task task = taskQueue.poll(); + if (task == null) return; + task.execute(deltaTime); + if (!task.isComplete()) taskQueue.add(task); + } +} diff --git a/scheduler/src/main/java/com/iluwatar/scheduler/ShortestRemainingTimeFirstScheduler.java b/scheduler/src/main/java/com/iluwatar/scheduler/ShortestRemainingTimeFirstScheduler.java new file mode 100644 index 00000000000..fdfd55280ad --- /dev/null +++ b/scheduler/src/main/java/com/iluwatar/scheduler/ShortestRemainingTimeFirstScheduler.java @@ -0,0 +1,28 @@ +package com.iluwatar.scheduler; + +import java.util.Comparator; +import java.util.PriorityQueue; +import java.util.Queue; + +public class ShortestRemainingTimeFirstScheduler implements TaskScheduler { + private final Queue taskQueue = + new PriorityQueue<>( + (task1, task2) -> { + if (task2.getRemainingTime() != task1.getRemainingTime()) + return task1.getRemainingTime() - task2.getRemainingTime(); + return task1.getId() - task2.getId(); // lower id (earlier task) has higher priority + }); + + @Override + public void scheduleTask(Task task) { + taskQueue.add(task); + } + + @Override + public void update(int deltaTime) { + Task task = taskQueue.poll(); + if (task == null) return; + task.execute(deltaTime); + if (!task.isComplete()) taskQueue.add(task); + } +} diff --git a/scheduler/src/main/java/com/iluwatar/scheduler/Simulator.java b/scheduler/src/main/java/com/iluwatar/scheduler/Simulator.java new file mode 100644 index 00000000000..87f6830950e --- /dev/null +++ b/scheduler/src/main/java/com/iluwatar/scheduler/Simulator.java @@ -0,0 +1,52 @@ +package com.iluwatar.scheduler; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import lombok.RequiredArgsConstructor; + +/** Simulate scheduler schedule tasks. */ +@RequiredArgsConstructor +public class Simulator implements PropertyChangeListener { + private final TaskScheduler scheduler; + + /** Map time to tasks that need to be scheduled at that time. */ + private final Map> tasks; + + private final int deltaTime; + private final int simulateTime; + private final LinkedHashMap taskCompletedOrder = new LinkedHashMap<>(); + private int elapsedTime = 0; + + public LinkedHashMap simulate() { + while (elapsedTime < simulateTime) { + if (tasks.containsKey(elapsedTime)) { + for (Task task : tasks.get(elapsedTime)) { + task.getSupport().addPropertyChangeListener(this); + scheduler.scheduleTask(task); + } + } + scheduler.update(deltaTime); + elapsedTime += deltaTime; + } + return taskCompletedOrder; + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (Task.COMPLETE_PROPERTY.equals(evt.getPropertyName())) { + onTaskComplete(evt); + } + } + + private void onTaskComplete(PropertyChangeEvent evt) { + Task task = (Task) evt.getSource(); + /* + elapsedTime is updated after task dispatch complete event to simulator, + so we need to add deltaTime + */ + taskCompletedOrder.put(task.getId(), elapsedTime + deltaTime); + } +} diff --git a/scheduler/src/main/java/com/iluwatar/scheduler/Task.java b/scheduler/src/main/java/com/iluwatar/scheduler/Task.java new file mode 100644 index 00000000000..fcf8e7d7305 --- /dev/null +++ b/scheduler/src/main/java/com/iluwatar/scheduler/Task.java @@ -0,0 +1,44 @@ +package com.iluwatar.scheduler; + +import java.beans.PropertyChangeSupport; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class Task { + public static final String COMPLETE_PROPERTY = "complete"; + private final PropertyChangeSupport support = new PropertyChangeSupport(this); + private final int id; + private final int totalExecutionTime; + + /** The priority of the task. The higher the number, the higher the priority. */ + private int priority = 0; + + private int elapsedTime = 0; + private boolean complete = false; + + public Task(int id, int totalExecutionTime, int priority) { + this.id = id; + this.totalExecutionTime = totalExecutionTime; + this.priority = priority; + } + + public void execute(int seconds) { + if (complete) throw new IllegalStateException("Task already completed"); + + elapsedTime += seconds; + + // Uncomment the following line to see the execution of tasks + // System.out.printf("%d, %d/%d\n", id, elapsedTime, totalExecutionTime); + + if (elapsedTime >= totalExecutionTime) { + complete = true; + support.firePropertyChange(COMPLETE_PROPERTY, false, true); + } + } + + public int getRemainingTime() { + return totalExecutionTime - elapsedTime; + } +} diff --git a/scheduler/src/main/java/com/iluwatar/scheduler/TaskScheduler.java b/scheduler/src/main/java/com/iluwatar/scheduler/TaskScheduler.java new file mode 100644 index 00000000000..f353d56fb42 --- /dev/null +++ b/scheduler/src/main/java/com/iluwatar/scheduler/TaskScheduler.java @@ -0,0 +1,10 @@ +package com.iluwatar.scheduler; + + +public interface TaskScheduler { + /** Add task to the scheduler */ + void scheduleTask(Task task); + + /** Update to execute scheduled tasks */ + void update(int deltaTime); +} diff --git a/scheduler/src/test/java/com/iluwatar/scheduler/FirstComeFirstServedSchedulerTest.java b/scheduler/src/test/java/com/iluwatar/scheduler/FirstComeFirstServedSchedulerTest.java new file mode 100644 index 00000000000..f291f8db4fe --- /dev/null +++ b/scheduler/src/test/java/com/iluwatar/scheduler/FirstComeFirstServedSchedulerTest.java @@ -0,0 +1,49 @@ +package com.iluwatar.scheduler; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class FirstComeFirstServedSchedulerTest { + @Test + void testExecuteTasksFromBeginning() { + Map> tasks = new HashMap<>(); + tasks.put(0, List.of(new Task(1, 3), new Task(2, 2), new Task(3, 4))); + + Simulator simulator = new Simulator(new FirstComeFirstServedScheduler(), tasks, 1, 100); + + LinkedHashMap taskCompletedOrder = simulator.simulate(); + + assertEquals(3, taskCompletedOrder.size()); + assertIterableEquals(List.of(1, 2, 3), taskCompletedOrder.keySet()); + assertEquals(3, taskCompletedOrder.get(1)); + assertEquals(5, taskCompletedOrder.get(2)); + assertEquals(9, taskCompletedOrder.get(3)); + } + + @Test + void testExecuteTasks() { + Map> tasks = new HashMap<>(); + tasks.put(0, List.of(new Task(1, 3))); + tasks.put(1, List.of(new Task(2, 2))); + tasks.put(6, List.of(new Task(3, 4))); + tasks.put(7, List.of(new Task(4, 2))); + tasks.put(8, List.of(new Task(5, 1))); + + Simulator simulator = new Simulator(new FirstComeFirstServedScheduler(), tasks, 1, 100); + + LinkedHashMap taskCompletedOrder = simulator.simulate(); + + assertEquals(5, taskCompletedOrder.size()); + assertIterableEquals(List.of(1, 2, 3, 4, 5), taskCompletedOrder.keySet()); + assertEquals(3, taskCompletedOrder.get(1)); + assertEquals(5, taskCompletedOrder.get(2)); + assertEquals(10, taskCompletedOrder.get(3)); + assertEquals(12, taskCompletedOrder.get(4)); + assertEquals(13, taskCompletedOrder.get(5)); + } +} diff --git a/scheduler/src/test/java/com/iluwatar/scheduler/PrioritySchedulerTest.java b/scheduler/src/test/java/com/iluwatar/scheduler/PrioritySchedulerTest.java new file mode 100644 index 00000000000..a98ac820203 --- /dev/null +++ b/scheduler/src/test/java/com/iluwatar/scheduler/PrioritySchedulerTest.java @@ -0,0 +1,49 @@ +package com.iluwatar.scheduler; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class PrioritySchedulerTest { + @Test + void testExecuteTasksFromBeginning() { + Map> tasks = new HashMap<>(); + tasks.put(0, List.of(new Task(1, 3, 0), new Task(2, 2, 2), new Task(3, 1, 1))); + + Simulator simulator = new Simulator(new PriorityScheduler(), tasks, 1, 10); + + LinkedHashMap taskCompletedOrder = simulator.simulate(); + + assertEquals(3, taskCompletedOrder.size()); + assertIterableEquals(List.of(2, 3, 1), taskCompletedOrder.keySet()); + assertEquals(2, taskCompletedOrder.get(2)); + assertEquals(3, taskCompletedOrder.get(3)); + assertEquals(6, taskCompletedOrder.get(1)); + } + + @Test + void testExecuteTasks() { + Map> tasks = new HashMap<>(); + tasks.put(0, List.of(new Task(1, 3, 1))); + tasks.put(1, List.of(new Task(2, 2, 0))); + tasks.put(2, List.of(new Task(3, 4, 5))); + tasks.put(7, List.of(new Task(4, 2, 4))); + tasks.put(8, List.of(new Task(5, 1, 2))); + + Simulator simulator = new Simulator(new PriorityScheduler(), tasks, 1, 100); + + LinkedHashMap taskCompletedOrder = simulator.simulate(); + + assertEquals(5, taskCompletedOrder.size()); + assertIterableEquals(List.of(3, 1, 4, 5, 2), taskCompletedOrder.keySet()); + assertEquals(6, taskCompletedOrder.get(3)); + assertEquals(7, taskCompletedOrder.get(1)); + assertEquals(9, taskCompletedOrder.get(4)); + assertEquals(10, taskCompletedOrder.get(5)); + assertEquals(12, taskCompletedOrder.get(2)); + } +} diff --git a/scheduler/src/test/java/com/iluwatar/scheduler/RoundRobinSchedulerTest.java b/scheduler/src/test/java/com/iluwatar/scheduler/RoundRobinSchedulerTest.java new file mode 100644 index 00000000000..3be43346a7c --- /dev/null +++ b/scheduler/src/test/java/com/iluwatar/scheduler/RoundRobinSchedulerTest.java @@ -0,0 +1,45 @@ +package com.iluwatar.scheduler; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.*; +import org.junit.jupiter.api.Test; + +class RoundRobinSchedulerTest { + @Test + void testExecuteTasksFromBeginning() { + Map> tasks = new HashMap<>(); + tasks.put(0, List.of(new Task(1, 3), new Task(2, 2), new Task(3, 4))); + + Simulator simulator = new Simulator(new RoundRobinScheduler(), tasks, 1, 10); + + LinkedHashMap taskCompletedOrder = simulator.simulate(); + assertEquals(3, taskCompletedOrder.size()); + assertIterableEquals(List.of(2, 1, 3), taskCompletedOrder.keySet()); + assertEquals(5, taskCompletedOrder.get(2)); + assertEquals(7, taskCompletedOrder.get(1)); + assertEquals(9, taskCompletedOrder.get(3)); + } + + @Test + void testExecuteTasks() { + Map> tasks = new HashMap<>(); + tasks.put(0, List.of(new Task(1, 3))); + tasks.put(1, List.of(new Task(2, 2))); + tasks.put(2, List.of(new Task(3, 4))); + tasks.put(7, List.of(new Task(4, 2))); + tasks.put(8, List.of(new Task(5, 1))); + + Simulator simulator = new Simulator(new RoundRobinScheduler(), tasks, 1, 100); + + LinkedHashMap taskCompletedOrder = simulator.simulate(); + + assertEquals(5, taskCompletedOrder.size()); + assertIterableEquals(List.of(1, 2, 3, 5, 4), taskCompletedOrder.keySet()); + assertEquals(4, taskCompletedOrder.get(1)); + assertEquals(6, taskCompletedOrder.get(2)); + assertEquals(10, taskCompletedOrder.get(3)); + assertEquals(11, taskCompletedOrder.get(5)); + assertEquals(12, taskCompletedOrder.get(4)); + } +} diff --git a/scheduler/src/test/java/com/iluwatar/scheduler/ShortestRemainingTimeFirstSchedulerTest.java b/scheduler/src/test/java/com/iluwatar/scheduler/ShortestRemainingTimeFirstSchedulerTest.java new file mode 100644 index 00000000000..e50a3acae4f --- /dev/null +++ b/scheduler/src/test/java/com/iluwatar/scheduler/ShortestRemainingTimeFirstSchedulerTest.java @@ -0,0 +1,45 @@ +package com.iluwatar.scheduler; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class ShortestRemainingTimeFirstSchedulerTest { + @Test + void testExecuteTasksFromBeginning() { + Map> tasks = new HashMap<>(); + tasks.put(0, List.of(new Task(1, 3), new Task(2, 2), new Task(3, 4))); + + Simulator simulator = new Simulator(new ShortestRemainingTimeFirstScheduler(), tasks, 1, 10); + + LinkedHashMap taskCompletedOrder = simulator.simulate(); + assertEquals(3, taskCompletedOrder.size()); + assertIterableEquals(List.of(2, 1, 3), taskCompletedOrder.keySet()); + } + + @Test + void testExecuteTasks() { + Map> tasks = new HashMap<>(); + tasks.put(0, List.of(new Task(1, 3))); + tasks.put(1, List.of(new Task(2, 2))); + tasks.put(2, List.of(new Task(3, 4))); + tasks.put(7, List.of(new Task(4, 2))); + tasks.put(8, List.of(new Task(5, 1))); + + Simulator simulator = new Simulator(new ShortestRemainingTimeFirstScheduler(), tasks, 1, 100); + + LinkedHashMap taskCompletedOrder = simulator.simulate(); + + assertEquals(5, taskCompletedOrder.size()); + assertIterableEquals(List.of(1, 2, 3, 5, 4), taskCompletedOrder.keySet()); + assertEquals(3, taskCompletedOrder.get(1)); + assertEquals(5, taskCompletedOrder.get(2)); + assertEquals(9, taskCompletedOrder.get(3)); + assertEquals(10, taskCompletedOrder.get(5)); + assertEquals(12, taskCompletedOrder.get(4)); + } +}