Initial commit

This commit is contained in:
github-classroom[bot]
2022-03-03 11:27:40 +00:00
commit 8ca02de8bc
47 changed files with 15983 additions and 0 deletions
+60
View File
@@ -0,0 +1,60 @@
/*
* Gradle build configuration for specific lab module / exercise
*/
// the Java plugin is added by default in the main lab configuration
plugins {
id 'java'
// Apply the application plugin to add support for building a CLI application.
id 'application'
// Adding JavaFX support and dependencies
id 'org.openjfx.javafxplugin' version '0.0.11'
}
// Project/Module information
description = 'Lab01 Mandelbrot'
group = 'ch.zhaw.prog2'
version = '2022.1'
// Dependency configuration
// repositories to download dependencies from
repositories {
mavenCentral()
}
// required dependencies
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2'
testImplementation 'org.mockito:mockito-core:4.3.1'
}
// Test task configuration
test {
// Use JUnit platform for unit tests
useJUnitPlatform()
}
// Configuration for Application plugin
application {
// Define the main class for the application.
mainClass = 'ch.zhaw.prog2.mandelbrot.Mandelbrot'
}
// Configuration for JavaFX plugin
javafx {
version = '17'
modules = [ 'javafx.controls', 'javafx.fxml' ]
}
// Java plugin configuration
java {
// By default the Java version of the gradle process is used as source/target version.
// This can be overridden, to ensure a specific version. Enable only if required.
// sourceCompatibility = JavaVersion.VERSION_17 // ensure Java source code compatibility
// targetCompatibility = JavaVersion.VERSION_17 // version of the created byte-code
// Java compiler specific options
compileJava {
// source files should be UTF-8 encoded
options.encoding = 'UTF-8'
// for more options see https://docs.gradle.org/current/dsl/org.gradle.api.tasks.compile.CompileOptions.html
}
}
@@ -0,0 +1,17 @@
package ch.zhaw.prog2.mandelbrot;
import javafx.scene.paint.Color;
/**
* This is a container class which holds the data for one row to be drawn on the canvas.
* No getter and setters. Just use direct access to the fields.
*/
public class ImageRow {
public final int rowNumber;
public final Color[] pixels;
public ImageRow(int rowNumber, int width) {
this.rowNumber = rowNumber;
this.pixels = new Color[width];
}
}
@@ -0,0 +1,27 @@
package ch.zhaw.prog2.mandelbrot;
import javafx.application.Application;
/**
* This application uses several threads to compute an image "in the background".
*
* As rows of pixels in the image are computed, they are copied to the screen.
* (The image is a small piece of the famous Mandelbrot set, which
* is used just because it takes some time to compute. There is no need
* to understand what the image means.) The user starts the computation by
* clicking a "Start" button. A pop-up menu allows the user to select the
* number of threads to be used. The specified number of threads is created
* and each thread is assigned a region in the image. The threads are run
* at lower priority, which will make sure that the GUI thread will get a
* chance to run to repaint the display as necessary.
*/
public class Mandelbrot {
/**
* This Wrapper Class is only required to allow IDEs to start the FX-Applications
*/
public static void main(String[] args) {
Application.launch(MandelbrotGui.class, args);
}
} // end Mandelbrot
@@ -0,0 +1,206 @@
package ch.zhaw.prog2.mandelbrot;
import ch.zhaw.prog2.mandelbrot.processors.*;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.stage.Screen;
import javafx.stage.Stage;
/**
* This application uses several threads to compute an image "in the background".
*
* As rows of pixels in the image are computed, they are copied to the screen.
* (The image is a small piece of the famous Mandelbrot set, which
* is used just because it takes some time to compute. There is no need
* to understand what the image means.) The user starts the computation by
* clicking a "Start" button. A pop-up menu allows the user to select the
* number of threads to be used. The specified number of threads is created
* and each thread is assigned a region in the image. The threads are run
* at lower priority, which will make sure that the GUI thread will get a
* chance to run to repaint the display as necessary.
*/
public class MandelbrotGui extends Application implements MandelbrotProcessorListener {
// possible states of the GUI
private enum GuiState {START, RUNNING, STOPPING, STOPPED}
private GuiState state = GuiState.START;
private MandelbrotProcessor processor;
private Button startButton; // button the user can click to start or abort the thread
private ComboBox<String> threadCountSelect; // for specifying the number of threads to be used
private ComboBox<String> processorSelect; // for specifying the processor
private Canvas canvas; // the canvas where the image is displayed
private GraphicsContext g; // the graphics context for drawing on the canvas
private int width, height; // the size of the canvas
/**
* Set up the GUI and event handling. The canvas will be 1200-by-1000 pixels,
* if that fits comfortably on the screen; otherwise, size will be reduced to fit.
*/
public void start(Stage stage) {
int screenWidth = (int) Screen.getPrimary().getVisualBounds().getWidth();
int screenHeight = (int) Screen.getPrimary().getVisualBounds().getHeight();
width = Math.min(1200, screenWidth - 50);
height = Math.min(1000, screenHeight - 120);
canvas = new Canvas(width, height);
g = canvas.getGraphicsContext2D();
g.setFill(Color.LIGHTGRAY);
g.fillRect(0, 0, width, height);
startButton = new Button("Start!");
startButton.setOnAction(e -> startOrStopProcessing());
// Because the tasks are CPU intensive, the default number of threads should be equal to the number of cores
int numCores = Runtime.getRuntime().availableProcessors();
int maxThreads = 2 * numCores;
threadCountSelect = new ComboBox<>();
threadCountSelect.setEditable(false);
for (int i = 1; i <= maxThreads; i++) {
threadCountSelect.getItems().add("Use " + i + " threads.");
}
// use the number of cores as default value
threadCountSelect.getSelectionModel().select(numCores - 1);
processorSelect = new ComboBox<>();
processorSelect.setEditable(false);
processorSelect.getItems().add("Use Thread-Processor");
processorSelect.getItems().add("Use Executor-Processor");
processorSelect.getItems().add("Use Callable-Processor");
processorSelect.getSelectionModel().select(0);
HBox bottom = new HBox(8, startButton, threadCountSelect, processorSelect);
bottom.setStyle("-fx-padding: 6px; -fx-border-color:black; -fx-border-width: 2px 0 0 0");
bottom.setAlignment(Pos.CENTER);
BorderPane root = new BorderPane(canvas);
root.setBottom(bottom);
root.setStyle("-fx-border-color:black; -fx-border-width: 2px");
Scene scene = new Scene(root);
stage.setScene(scene);
stage.setTitle("Mandelbrot");
stage.setResizable(false);
stage.show();
}
/**
* This method is called when the user clicks the start button.
* If no computation is currently running, it starts processing with
* as many new threads as the user has specified.
* If a computation is in progress when this method is called,
* the processing is stopped and threads should be terminated.
*/
private void startOrStopProcessing() {
if (state == GuiState.RUNNING) {
setState(GuiState.STOPPING);
if (processor != null) {
processor.stopProcessing();
}
} else {
setState(GuiState.RUNNING);
int threadCount = threadCountSelect.getSelectionModel().getSelectedIndex() + 1;
processor = getProcessor();
System.out.println("Start processing using " + processor.getClass().getSimpleName() +
" with " + threadCount + " threads.");
// run processing in its own thread
new Thread(() -> processor.startProcessing(threadCount)).start();
}
}
/**
* Create an instance of the selected MandelbrotProcessor implementation.
*
* @return new Instance of the selected MandelbrotProcessor implementation
*/
private MandelbrotProcessor getProcessor() {
MandelbrotProcessor processor;
int processorIndex = processorSelect.getSelectionModel().getSelectedIndex();
switch (processorIndex) {
case 0: processor = new MandelbrotTaskProcessor(this, width, height); break;
case 1: processor = new MandelbrotExecutorProcessor(this, width, height); break;
case 2: processor = new MandelbrotCallableProcessor(this, width, height); break;
default: processor = new MandelbrotTaskProcessor(this, width, height); break;
}
return processor;
}
/**
* Update GUI elements for a specific state
*
* @param newState the new state the GUI should be configured for
*/
private void setState(GuiState newState) {
this.state = newState;
switch (newState) {
case RUNNING:
Platform.runLater(() -> {
startButton.setText("Abort"); // change name while computation is in progress
threadCountSelect.setDisable(true); // will be re-enabled when all threads finish
processorSelect.setDisable(true); // will be re-enabled when all threads finish
g.setFill(Color.LIGHTGRAY); // fill canvas with gray
g.fillRect(0, 0, width, height);
});
break;
case STOPPING:
Platform.runLater(() -> {
// prevent user from trying to stop threads that are already stopping
startButton.setDisable(true); // will be re-enabled when all threads have stopped
});
break;
case STOPPED:
Platform.runLater(() -> {
// Make sure state is correct when threads end.
startButton.setText("Start Again");
startButton.setDisable(false);
threadCountSelect.setDisable(false);
processorSelect.setDisable(false);
});
break;
default:
break;
}
}
/**
* This method is called from the Processor when the processing is stopped or completed.
* GUI state needs to be reset.
*
* @param duration Duration of the processing in Milliseconds
*/
@Override
public void processingStopped(long duration) {
this.setState(GuiState.STOPPED);
System.out.println("Finished processing after " + duration + "ms");
}
/**
* This method is called from the Processor when a row has been processed
* and needs to be added to the image.
*
* @param row the row of pixels whose colors are to be set
*/
@Override
public void rowProcessed(ImageRow row) {
if (row != null) {
Platform.runLater(() -> {
for (int x = 0; x < row.pixels.length; x++) {
// Color an individual pixel by filling in a 1-by-1 pixel rectangle.
g.setFill(row.pixels[x]);
g.fillRect(x, row.rowNumber, 1, 1);
}
});
}
}
} // end MandelbrotGui
@@ -0,0 +1,19 @@
package ch.zhaw.prog2.mandelbrot;
public interface MandelbrotProcessorListener {
/**
* This method is called from the Processor when the processing is stopped or completed.
* GUI state needs to be reset.
*
* @param duration Duration of the processing in Milliseconds
*/
void processingStopped(long duration);
/**
* This method is called from the Processor when a row has been processed
* and needs to be added to the image.
*
* @param row the row of pixels whose colors are to be set
*/
void rowProcessed(ImageRow row);
}
@@ -0,0 +1,102 @@
package ch.zhaw.prog2.mandelbrot.processors;
import ch.zhaw.prog2.mandelbrot.ImageRow;
import ch.zhaw.prog2.mandelbrot.MandelbrotProcessorListener;
import javafx.scene.paint.Color;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
public class MandelbrotCallableProcessor extends MandelbrotProcessor {
private volatile boolean terminate; // signal the threads to abort processing and terminate
/**
* Initialize the Mandelbrot processor.
* This method also initializes the color palette, containing colors in spectral order.
* @param processorListener class to notify about processing results
* @param width with of the canvas in pixel
* @param height height of the canvas in pixel
*/
public MandelbrotCallableProcessor(MandelbrotProcessorListener processorListener, int width, int height) {
super(processorListener, width, height);
}
/**
* This method starts as many new threads as the user has specified,
* and assigns a different part of the image to each thread.
* The threads are run at lower priority than the event-handling thread,
* in order to keep the GUI responsive.
*
* @param numThreads number of thread to start to run the tasks
*/
@Override
public void startProcessing(int numThreads) {
terminate = false; // Set the signal before starting the threads!
super.tasksRemaining = height; // Records how many of the threads are still running
super.startTime = System.currentTimeMillis();
// TODO: Start the the executor service with the given number of threads.
// TODO: process all rows using the Callable MandelbrotTask and store returned Futures in a list
long duration = System.currentTimeMillis()-startTime;
System.out.println("Tasks submitted after " + duration + "ms");
// TODO: get results from Future list and send them to the processListener (GUI)
// make sure to handle all Exceptions
try {
} finally {
// stop processing and shutdown executor
stopProcessing();
}
}
/**
* Stopp processing tasks and terminate all threads.
* Also notifies the GUI that the processing has been stopped.
*/
@Override
public void stopProcessing() {
terminate = true; // signal the threads to abort
// TODO: shutdown executor service
// calculate processing time
long duration = System.currentTimeMillis() - startTime;
// notify the listener that the processing is completed
processorListener.processingStopped(duration);
}
/**
* This class defines the thread that does the computation.
* The run method computes the image one pixel at a time.
* After computing the colors for each row of pixels, the colors are
* copied into the image, and the part of the display that shows that
* row is repainted.
*
* Extended to implement also Callable.
* the call() method calculates and return only the result for the startRow.
*/
private class MandelbrotTask implements Callable<ImageRow> {
// TODO: Use Task implementation from MandelbrotExecutorProcessor change it to a Callable.
public ImageRow call() {
return null; // Compute one row of pixels.
}
} // end MandelbrotTask
}
@@ -0,0 +1,80 @@
package ch.zhaw.prog2.mandelbrot.processors;
import ch.zhaw.prog2.mandelbrot.ImageRow;
import ch.zhaw.prog2.mandelbrot.MandelbrotProcessorListener;
import javafx.scene.paint.Color;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MandelbrotExecutorProcessor extends MandelbrotProcessor {
private volatile boolean terminate; // signal the threads to abort processing and terminate
/**
* Initialize the Mandelbrot processor.
* This method also initializes the color palette, containing colors in spectral order.
* @param processorListener class to notify about processing results
* @param width with of the canvas in pixel
* @param height height of the canvas in pixel
*/
public MandelbrotExecutorProcessor(MandelbrotProcessorListener processorListener, int width, int height) {
super(processorListener, width, height);
}
/**
* This method starts as many new threads as the user has specified,
* and assigns a different part of the image to each thread.
* The threads are run at lower priority than the event-handling thread,
* in order to keep the GUI responsive.
*
* @param numThreads number of thread to start to run the tasks
*/
@Override
public void startProcessing(int numThreads) {
terminate = false; // Set the signal before starting the threads!
super.tasksRemaining = height; // Records how many of the threads are still running
super.startTime = System.currentTimeMillis();
// TODO: Start the the executor service with the given number of threads.
// TODO: start a task for each row
}
/**
* Stopp processing tasks and terminate all threads.
* Also notifies the GUI that the processing has been stopped.
*/
@Override
public void stopProcessing() {
terminate = true; // signal the threads to abort
// TODO: shutdown executor service
// calculate processing time
long duration = System.currentTimeMillis() - startTime;
// notify the listener that the processing is completed
processorListener.processingStopped(duration);
}
/**
* This class defines the thread that does the computation.
* The run method computes the image one pixel at a time.
* After computing the colors for each row of pixels, the colors are
* copied into the image, and the part of the display that shows that
* row is repainted.
*/
private class MandelbrotTask implements Runnable {
// TODO: Use Task implementation from MandelbrotTaskProcessor and modify it to calculat only one row.
@Override
public void run() {
}
} // end MandelbrotTask
}
@@ -0,0 +1,60 @@
package ch.zhaw.prog2.mandelbrot.processors;
import ch.zhaw.prog2.mandelbrot.MandelbrotProcessorListener;
import javafx.scene.paint.Color;
public abstract class MandelbrotProcessor {
protected final MandelbrotProcessorListener processorListener;
protected javafx.scene.paint.Color[] palette; // the color palette, containing the colors of the spectrum
protected int width; // width of the canvas
protected int height; //height of the canvas
protected long startTime; // used to calculate the runtime for the calculation
protected int tasksRemaining; // How many tasks/threads are still running resp. need to be processed
/**
* Initialize the Mandelbrot processor.
* This method also initializes the color palette, containing colors in spectral order.
* @param processorListener class to notify about processing results
* @param width with of the canvas in pixel
* @param height height of the canvas in pixel
*/
public MandelbrotProcessor(MandelbrotProcessorListener processorListener, int width, int height) {
this.processorListener = processorListener;
this.width = width;
this.height = height;
// initialize the color palette
this.palette = new Color[256];
for (int i = 0; i < 256; i++) {
this.palette[i] = Color.hsb(360 * (i / 256.0), 1, 1);
}
}
/**
* This method starts as many new threads as the user has specified,
* and assigns a different part of the image to each thread.
*
* @param numThreads number of thread to start to run the tasks
*/
public abstract void startProcessing(int numThreads);
/**
* Stopp processing tasks and terminate all threads.
* Also notifies the GUI that the processing has been stopped.
*/
public abstract void stopProcessing();
/**
* This method is called by each task/thread when it terminates. We keep track
* of the number of tasks/threads that have terminated, so that when they have
* all finished, we can put the program into the correct state, such as
* changing the name of the button to "Start Again" and re-enabling the
* pop-up menu.
*/
protected synchronized void taskFinished() {
tasksRemaining--;
if (tasksRemaining == 0) { // all threads have finished
stopProcessing();
}
}
}
@@ -0,0 +1,156 @@
package ch.zhaw.prog2.mandelbrot.processors;
import ch.zhaw.prog2.mandelbrot.ImageRow;
import ch.zhaw.prog2.mandelbrot.MandelbrotProcessorListener;
import javafx.scene.paint.Color;
public class MandelbrotTaskProcessor extends MandelbrotProcessor {
private volatile boolean terminate; // signal the threads to abort processing and terminate
private java.lang.Thread[] workers; // the threads that compute the image
/**
* Initialize the Mandelbrot processor.
* This method also initializes the color palette, containing colors in spectral order.
* @param processorListener class to notify about processing results
* @param width with of the canvas in pixel
* @param height height of the canvas in pixel
*/
public MandelbrotTaskProcessor(MandelbrotProcessorListener processorListener, int width, int height) {
super(processorListener, width, height);
}
/**
* This method starts as many new threads as the user has specified,
* and assigns a different part of the image to each thread.
* The threads are run at lower priority than the event-handling thread,
* in order to keep the GUI responsive.
*
* @param numThreads number of thread to start to run the tasks
*/
@Override
public void startProcessing(int numThreads) {
terminate = false; // Set the signal before starting the threads!
// use numThread tasks each calculating a range of rows
super.tasksRemaining = numThreads; // Records how many of the threads are still running
super.startTime = System.currentTimeMillis();
// calculate number of rows each task needs to calculate
int rowsPerThread = height / numThreads;
// Start the given number of threads and process ranges of rows
workers = new Thread[numThreads];
for (int i = 0; i < numThreads; i++) {
// first row computed by thread number i
int startRow = rowsPerThread * i;
// last row computed by thread number i
// (we have to make sure that the endRow for the last thread is the bottom row of the image)
int endRow = (i == numThreads - 1) ? height - 1 : rowsPerThread * (i + 1) - 1;
// Create and start a thread to compute the rows of the image from startRow to endRow.
workers[i] = new Thread(new MandelbrotTask(startRow, endRow));
try {
workers[i].setPriority(Thread.currentThread().getPriority() - 1);
} catch (Exception e) {
System.out.println("Error: " + e.getMessage());
}
workers[i].start();
}
}
/**
* Stopp processing tasks and terminate all threads.
* Also notifies the GUI that the processing has been stopped.
*/
@Override
public void stopProcessing() {
terminate = true; // signal the threads to abort
// shutdown / unset workers
workers = null;
// calculate processing time
long duration = System.currentTimeMillis() - startTime;
// notify the listener that the processing is completed
processorListener.processingStopped(duration);
}
/**
* This class defines the thread that does the computation.
* The run method computes the image one pixel at a time.
* After computing the colors for each row of pixels, the colors are
* copied into the image, and the part of the display that shows that
* row is repainted.
* All modifications to the GUI are made using Platform.runLater().
* (Since the thread runs in the background, at lower priority than
* the event-handling thread, the event-handling thread wakes up
* immediately to repaint the display.)
*/
private class MandelbrotTask implements Runnable {
// these values define the area and depth of the Mandelbrot graphic
// we keep them local to allow to extend the function to
// select the area and depth dynamically.
private final double xmin, xmax, ymin, ymax, dx, dy;
private final int maxIterations;
// this tasks calculates the following range of rows
private final int startRow, endRow;
/** initialize the Task to calculate a single row */
MandelbrotTask(int row) {
this(row, row);
}
/** initialize the Task to calculate a range of rows */
MandelbrotTask(int startRow, int endRow) {
this.startRow = startRow;
this.endRow = endRow;
xmin = -1.6744096740931858;
xmax = -1.674409674093473;
ymin = 4.716540768697223E-5;
ymax = 4.716540790246652E-5;
dx = (xmax - xmin) / (width - 1);
dy = (ymax - ymin) / (height - 1);
maxIterations = 10000;
}
public void run() {
try {
for (int row = startRow; row <= endRow; row++) {
// Compute one row of pixels.
ImageRow imageRow = calculateRow(row);
// Check for the signal to immediately abort the computation.
if (terminate || imageRow == null) return;
// notify the listener about the processed image row
processorListener.rowProcessed(imageRow);
}
} finally {
// Make sure this is called when the task finishes for any reason.
taskFinished(); // notify task completion; calls stopProcessing() when last tasks completed
}
}
private ImageRow calculateRow(int row) {
final ImageRow imageRow = new ImageRow(row, width);
double x;
double y = ymax - dy * row;
for (int col = 0; col < width; col++) {
x = xmin + dx * col;
int count = 0;
double xx = x;
double yy = y;
while (count < maxIterations && (xx * xx + yy * yy) < 4) {
count++;
double newxx = xx * xx - yy * yy + x;
yy = 2 * xx * yy + y;
xx = newxx;
}
// select color based on count of iterations
imageRow.pixels[col] = (count != maxIterations) ?
palette[count % palette.length] : Color.BLACK;
// Check for the signal to immediately abort the computation.
if (terminate) return null;
}
return imageRow;
}
} // end MandelbrotTask
}
@@ -0,0 +1,98 @@
package ch.zhaw.prog2.mandelbrot;
import ch.zhaw.prog2.mandelbrot.processors.MandelbrotCallableProcessor;
import ch.zhaw.prog2.mandelbrot.processors.MandelbrotExecutorProcessor;
import ch.zhaw.prog2.mandelbrot.processors.MandelbrotProcessor;
import ch.zhaw.prog2.mandelbrot.processors.MandelbrotTaskProcessor;
import javafx.scene.paint.Color;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.Arrays;
import java.util.stream.Collectors;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
public class MandelbrotMockTest {
private static final int width = 10;
private static final int height = 5;
@Mock private MandelbrotProcessorListener listener;
private MandelbrotProcessorListener mandelbrotProcessorListenerTester = new MandelbrotProcessorListener() {
@Override
public void processingStopped(long duration) {
System.out.println("Duration(ms): " + duration);
assertNotEquals(0,duration, "Duration must not be 0");
}
@Override
public void rowProcessed(ImageRow row) {
assertNotNull(row);
assertNotNull(row.pixels);
assertEquals(width, row.pixels.length, "Length of Pixel-Array not matching");
System.out.print("row-nr: " + row.rowNumber + " ");
System.out.print("row-pixels: " + Arrays.stream(row.pixels)
.map(Color::toString).collect(Collectors.joining(", ")));
System.out.println();
}
};
@BeforeEach
public void setUp() throws Exception {
MockitoAnnotations.openMocks(this);
}
@Test
public void testMandelbrotTaskProcessorListenerCalls() throws InterruptedException {
MandelbrotProcessor processor = new MandelbrotTaskProcessor(listener, width, height);
processor.startProcessing(1);
Thread.sleep(2000);
verify(listener, times(height)).rowProcessed(any(ImageRow.class));
verify(listener, times(1)).processingStopped(anyLong());
}
@Test
public void testMandelbrotTaskProcessorListenerContent() throws InterruptedException {
MandelbrotProcessor processor = new MandelbrotTaskProcessor(mandelbrotProcessorListenerTester, width, height);
processor.startProcessing(1);
Thread.sleep(2000);
}
@Test
public void testMandelbrotExecutorProcessorListenerCalls() throws InterruptedException {
MandelbrotProcessor processor = new MandelbrotExecutorProcessor(listener, width, height);
processor.startProcessing(1);
Thread.sleep(2000);
verify(listener, times(height)).rowProcessed(any(ImageRow.class));
verify(listener, times(1)).processingStopped(anyLong());
}
@Test
public void testMandelbrotExecutorProcessorListenerContent() throws InterruptedException {
MandelbrotProcessor processor = new MandelbrotExecutorProcessor(mandelbrotProcessorListenerTester, width, height);
processor.startProcessing(1);
Thread.sleep(2000);
}
@Test
public void testMandelbrotCallableProcessorListenerCalls() throws InterruptedException {
MandelbrotProcessor processor = new MandelbrotCallableProcessor(listener, width, height);
processor.startProcessing(1);
Thread.sleep(2000);
verify(listener, times(height)).rowProcessed(any(ImageRow.class));
verify(listener, times(1)).processingStopped(anyLong());
}
@Test
public void testMandelbrotCallableProcessorListenerContent() throws InterruptedException {
MandelbrotProcessor processor = new MandelbrotCallableProcessor(mandelbrotProcessorListenerTester, width, height);
processor.startProcessing(1);
Thread.sleep(2000);
}
}