$30
For this assignment, your will profile a Conway's Game of Life
simulation, and improve its performance by refactoring several methods (to be
determined by the results of the profiling). This will consist of several
parts:
1. Profiling (before) to determine which method is the most CPU-intensive
2. Adding pinning tests (in the form of JUnit tests) to show that the functionality is unchanged by your modifications
2. Refactoring the method to be more performant while making sure your pinning tests still pass
3. Profiling (after) showing that your rewrite helped make your method more performant
The code is available under the src/ directory.
## How to Run SlowLifeGUI
1. Running GameOfLife. For Windows do (for running SlowLifeGUI with argument 5):
```
run.bat 5
```
For Mac / Linux do (for running SlowLifeGUI with argument 5):
```
bash run.sh 5
```
1. Running the TestRunner on GameOfLifePinningTest. For Windows do:
```
runTest.bat
```
For Mac / Linux do:
```
bash runTest.sh
```
Alternatively, I've created an Eclipse project for you so you can use Eclipse to import the existing project.
## What do do
The program is an implementation of Conway's Game of Life
(https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life). You can change the
state of a cell (from living to dead) by clicking on one of the buttons. Cells
which are currently alive have an X and a red background; cells that are dead
now, but were at any point alive during the current run, will have a green
background.
There are several other buttons which invoke different features:
1. Run - this will run one iteration of the Game of Life
2. Run Continuous - This will run iterations until you press the Stop button.
3. Stop - This will stop the current "Run Continuous" run. It will have no effect if the program is not running continuously already.
4. Write - This will write the state of the system to a backup file, to be loaded later.
5. Undo - This will undo the previous iteration. It will only work for one iteration (that is, you cannot do multiple undos to get back multiple iterations).
6. Load - This will load a previously-saved backup file (created using the Write button) to the current world.
7. Clear - This will clear the current world.
The application accepts one command line argument, specifying the size of the
world (e.g., if you enter 10, then you will create a 10 x 10 world).
### Task 1: Profiling using VisualVM
For the purposes of performance testing, we will focus on a 5 X 5 world. For
the initial pattern, we will use the "blinker" pattern shown in:
https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life#Examples_of_patterns
The actual pattern GIF is at:
https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life#/media/File:Game_of_life_blinker.gif
We will start from the vertical bar on a 5X5 matrix as shown in the GIF.
For an actual full performance test suite, we would have to try multiple world
sizes and multiple patterns but for the purposes of this deliverable, we will
focus on performance debugging only the above scenario. As it happens, once we
debug the above scenario, the application will start running quickly for all
scenarios.
There are exactly **THREE** major performance issues with **THREE** methods in
the code. They could be in any feature of the program! I recommend you try
exploratory testing to try out different features to determine which features
may have performance problems before profiling the application. There are
**TWO** features that have problems (that is, two buttons). The three
performance problems are dispersed in those two features.
In order to determine the "hot spots" of the application, you will need to run
a profiler such as VisualVM (download at https://visualvm.github.io/). Using a
profiler, determine the THREE methods you can modify to measurably increase the
speed of the application without modifying behavior. Refer to Exercise 4 for a
detailed explanation of how to use VisualVM to profile an application.
Now there is one more step that you have to do on VisualVM not explained in
Exercise 4: you need to replace "GameOfLife" with "*" in the "Profile classes:"
window on the right before pressing on the "CPU" button to start profiling.
This instructs VisualVM to not only the GameOfLife class (the class with the
main method), but all classes in the application. You did not need to do this
for the Exercise 4 MonkeySim application because it was single-threaded
application. All code in a single-threaded application execute starting from
the main method, so the default behavior of VisualVM to instrument starting
from the main method class was just fine. GameOfLife is a GUI application and
in a Java GUI application, there are multiple event handler threads running in
the background to handle events like button presses concurrently with the
application. In this case, the code for these threads do not start from the
main method.
### Task 2: Writing Pinning Tests for the Three Methods
Before doing refactoring any method, you should create "pinning tests" (as
described in the section on legacy code earlier - please review the slides on
Writing Testable Code if you need a refresher). These pinning tests should
check that the behavior of a modified method was not changed by your refactor.
The methods should work EXACTLY the same as before, except they should be
faster and take up less CPU time. **There should be at least one pinning test
per method refactored.**
In general, a pinning test doesn't necessarily have to be a unit test; it can
be an end-to-end test that you slap on quickly for the purposes of refactoring
(without spending the effort to localize tests by mocking external objects).
The end-to-end pinning test is then gradually refined into more high quality
unit tests. Sometimes this 2-step process is necessary because sometimes you
cannot write high quality unit tests before refactoring to make the code more
testable (e.g. via dependency injection). So you need a temporary end-to-end
pinning test to protect the code base meanwhile. For this deliverable, there
is no reason you cannot write unit tests from the get-go for pinning tests as
the dependency injection(s) has already been done for you. An example is the
**setCells** method in MainPanel.
Here are some requirements for your pinning tests:
1. You will use the 5 X 5 blinker pattern that I described above when a pattern
is required:
https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life#/media/File:Game_of_life_blinker.gif
The vertical bar pattern should be your precondition and the next horizontal
bar pattern should be your postcondition. **For the postcondition, make sure you
check all 25 cells in the 5 X 5 pattern**.
1. You are required to localize each pinning unit test within the tested class
as we did for Deliverable 2 (meaning it should not exercise any code from
external classes). You will have to use Mockito mock objects to achieve this.
1. Also, you may have to use behavior verification instead of state
verification to test some methods because the state change happens within a
mocked external object. Remember that you can use behavior verification only
on mocked objects (technically, you can use Mockito.verify on real objects too
using something called a Spy, but you won't need it for this deliverable). You
will get point deductions if you don't use mock objects and behavior
verification appropriately.
1. Note that even though the class is named GameOfLifePinningTest, the methods
you test will not necessarily come from the GameOfLife class. You will
create whatever objects from whatever classes are necessary to test the three
refactored methods. Hint: there is no reason for you to create a GameOfLife
object as there are no methods that you need to refactor there.
You will write all your pinning tests in the class GameOfLifePinningTest by
completing the TODOs. Please heed the comments. Just like for Deliverable 2,
you can add a Java stack trace to the error message to get information about
why your tests are failing by inserting the following line below
TestRunner.java line 24:
```
System.out.println(f.getTrace());
```
### Task 3: Refactor the Three Methods
Now refactor the three methods so that they are no longer performance problems.
If you look carefully, the three methods do a lot of wasted work for no reason.
It should be easy to refactor my removing that work. Make sure that your
pinning tests pass after refactoring.