Lab 09
In this lab session you will be training advanced build system concepts, such as build lifcycle phases, artifact
sideloading, and accessing third party artifact repositories.
You will be working with the XoxInternals code, which is a full working Model & Controller implementation the TicTacToe
game (paraphrased as Xox, for the X's and the O's).
To clone the sources, use: git clone https://github.com/m5c/XoxInternals.git
Maven lifecycle
Maven has three built-in lifecycles, each consisting of phases. We're mostly interested in the default lifecycle, which allows building software from sources.
Build by calling lifecycle phases
- The maven command always expects a lifecycle phase as argument.
- For every phase provided, the entire lifecycle until that phase is executed.
- E.g.
mvn packagecommand will execute all default lifecycle phases until (and including) thepackagephase.
Your turn
- You will be working with a new project
XoxFrontend, which will call some of the functionality provided by the clonedXoxInternalsproject.- However it is important that we want to connect these two projects, using maven, NOT by copy-pasting code from one project to the other.
-
For a start, initialize a new repo, called
XoxFrontend, by using the maven archetype command:mvn archetype:generate \ -DgroupId=ca.uqam.info \ -DartifactId=XoxFrontend \ -DarchetypeArtifactId=maven-archetype-quickstart \ -DinteractiveMode=falseIf you're on windows, type the command in a single line, and omit the antislashes.
-
The command will produce a new maven project of the following structure:
- Next build the project. Do so by calling the maven default lifecycle phase that only translates all java files into class files,
without producing a jar.
- Inspect the
targetfolder. Verify there areclassfiles, but nojarfile.
- Inspect the
- Build the project, by calling the maven default lifecycle phase that does the same as before, but also produces
a
jarfile.- Inspect the
targetfolder. Verify there areclassfiles, and ajarfile.
- Inspect the
Vulnerability scans
- Every project dependency is a potential liability. Whatever vulnerability is included in one of your dependencies, is potentially also a security risk for your own project.
- Luckily
pom.xmlfiles are an explicit form of declaring dependencies, that is, by inspecting thepom.xmlpotential vulnerabilities are easily detectable. - Searching (and fixing) vulnerable dependencies known as vulnerability scans.
IntelliJs vulnerability plugin
- Intellij has by-default-activated plugin named: Package Checker
- In class, you've seen a short demo of how to use the tool.
- When a
pom.xmlfile is opened, vulnerable dependencies are hihglightes with a different background - Hovering over the highlighted zones shows additional information on the vulnerability cause (and how to fix it)
- When a
Your turn
- Open your XoxFrontend project in IntelliJ.
- Double-click the
pom.xmlfile. - Carefully inspect the background colour of all lines. You'll see some lines highlighted by the Package Checker plugin.
- Hover your mouse over a highlighted line. Wait a second, a popup will appear.
- Double-click the
- Carefully read the message, it will tell you how to fix the vulnerability that you currently have in your project.
- Fix the vulnerability, and make sure your project can still be built.
Sideloading
- Sideloading refers to "outsmarting" maven's dependency resolve algorithm by artificially injecting an artifact
directly into the local
.m2respository. - This technique allows usage of dependencies that do not exist on the official online maven repository, and hence could not be automatically resolved by maven.
- The trick relies on the fact that maven will never search for an artifact online, once it has been found in the local repo.
Example:
XoxFrontendwill be needing functionality from the clonedXoxInternalsproject.- In this case
XoxFrontendcan declare a<dependency>in it'spom.xmlfile: - If we sideload
XoxInternalsinto the local.m2cache, the dependency is directly:- Maven does not need to search on online server for the artifact (we injected it ourselves)
XoxFrontendcan use the library code without code duplication.
- In this case
There are different ways to sideload artifacts into the local .m2 repository cache.
Do not copy code!
Do not copy past code from the cloned XoxInternals project. The goal is to use code from XoxInternals as maven dependency, not by code duplication.
Sideloading from maven projects
- The easiest form of sideloading occurs when the dependency in question is itself a maven project.
- Luckily this is the case.
XoxInternalshas apom.xml, and is itself a maven project. - In this case the
installphase can be used, to not only build an artifact, but also sideload it into the local cache.
Your turn
- Add the above
dependencyblock to theXoxFrontend'spom.xmlfile.- It will show up red. The dependency cannot be resolved, because no such artifact is on the official maven servers.
- Next: Inspect your local
.m2directory. It may be hidden on your system. If it is not visible in the file manager, open a terminal and access the hidden.m2directory on your home folder.- Inspect the content, verify there is no artifact
ca/uqam/info/xoxinternals
- Inspect the content, verify there is no artifact
- Build the previously cloned
XoxInternalsproject using theinstallphase. - Inspect your local
.m2cache again. You should not see a new artifact inca/uqam/info/xoxinternals. - Switch back to the
XoxFrontend'spom.xml, the dependency should now no longer show red, but be correctly resolved.
Next we want to see if we can actually use the code from the correctly resolved dependency:
- Add a new launcher to the XoxFrontend project that uses functionality from the XoxInternals project.
-
Use the below code, to initialize a new game instance between
For the two missing calls, you can use the below helper classes:AliceandBob: -
XoxGameInitiator.java:public class XoxGameInitiator { /** * Creates a new game instance for the given player names. * * @param playerName1 name of first player. * @param playerName2 name of second player. * @return long value of a unique game ID, that can be used to interact with game instance via * manager. */ public static long createNewGame(String playerName1, String playerName2) { Player player1 = new Player(playerName1, "#0000FF"); Player player2 = new Player(playerName2, "#00FF00"); List<Player> playerList = new LinkedList<Player>(); playerList.add(player1); playerList.add(player2); XoxInitSettings xoxSettings = new XoxInitSettings(playerList, playerName1); return XoxManagerImpl.getInstance().addGame(xoxSettings); } } XoxBoardAndActionPrinter.java:public class XoxBoardAndActionPrinter { public static void printBoardAndActionsForPlayer(long gameId, String player) { System.out.println(XoxManagerImpl.getInstance().getBoard(gameId)); // Print all possible actions for Alice XoxClaimFieldAction[] actions = XoxManagerImpl.getInstance().getActions(gameId, player); for (int i = 0; i < actions.length; i++) { System.out.println(actions[i]); } } }
Setup the maven exec plugin in your pom.xml and make sure you can compile and run your XoxFrontend with:
mvn clean compile exec:java
Sideloading from generic JAR files
- Often the library you're missing is itself not provided as source code, but you have only a jar file.
- Possibly the library is not even a maven project !
- It is still possible to sideload just from a jar file, directly into the local
.m2repository cache. - In the last course, you've seen a maven command to do just that.
Your turn
- Build the
XoxInternalsproject withmvn clean package - Copy the
jarfile from thetargetdirectory to your Desktop. - Delete the
XoxInternalsproject from your hard drive, but keep thejarfile. - Delete the
XoxInternalsartifact from your local.m2repository cache.
Clear you local repo!
The above steps are important. From the previous exercise you still have the XoxInternals artifact in your local repo.
At this point the
XoxFrontendproject will no longer work, because we've manually removed theXoxInternalsprojects it depends on.`
- Just using the saved
jarfile, use the mvn command seen in class to directly install an artifact from ajarfile. - Make sure to use the correct
groupId,artifactIdandversion, so the dependency can be correctly resolved. - Verify your local
.m2content and verify that you can now once more build and run theXoxFrontendapplication.
Third party repositories
Third party repositories are online folders providing unofficial maven artifacts. There are several reasons for placing artifacts in third party repos, notably access restriction and the ability to revoke.
Getting a dependency from a third party repo
- Instead of sideloading the
XoxInternalsartifact manually, it is preferrably to retrieve the artifact from a non-official third-party repo. - Reminder, maven's resolving algorithm is:
- First search to local cache for a match (local
.m2directory) - Only as fallback: search the third party repos (if defined)
- Only as last resort: searches the official maven servers.
- Sideloading targets the first step. Third party repos the second step.
In the next exercise you will be using a third party repository, instead of manually sideloading an artifact.
Your turn
- Delete the sideloaded Xox artifact from your local
~/.m2/repository/directory.
Clear you local repo!
The above steps are important. From the previous exercise you still have the XoxInternals artifact in your local repo. If you do not delete it, maven will stop after step 1 and never search for the artifact online.
- Try to compile your
XoxFrontendproject. It will fail, becauseXoxInternalscan no longer be resolved. - Access your
XoxFrontendproject'spom.xmland add a reference theinf2050repo (which is a non-officiel 3rd party maven repo, created specifically for this course): - Compile your project again with:
mvn clean test - Carefully inspect the output.
- Find the output line that indicates the missing artifact has been downloaded from the third party server.
- Verify the build was once more successful.
Why is there suddenly again a copy of XoxInternals in your local cache ?
Maven always caches artifacts locally, once they were successfully retrieved. Otherwise, you would never be able to work offline.
Build profiles
- Build profiles allow you to develop different version of an application, by simply specifying a profile name along the standard maven command.
- The syntax is
-P(for profile), directly followed by the profile name.- Note there is no space between the
-Pswitch and the profile name. - Example: mvn clean package
-Ppremium-software-version
- Note there is no space between the
Alternative launchers
- One use case of build profiles is the specification of alternative launchers.
- This way different functionality of a software can be started, or different hard coded parameters can be used.
Your turn
- Revise the build profile syntax presented in the last course and modify your XoxFrontend to contain two launcher classes:
- For the first launcher, you can reuse the one you coded for Alice and Bob, earlier in this lab session.
- A second launcher, to initialize a game between players
EveandBob.
- Modify the
XoxFrontendproject'spom.xml, so two new build profiles are defined:alice-and-bobas default profile.eve-and-bobas alternative profile.
- For each build profile, add a configuration of the
exec plugin, make sure that:- The
alice-and-bobprofile launches the originalXoxAliceAndBobjava launcher class. - The
eve-and-bobprofile launches the newly createdXoxEveAndBobjava launcher class.
- The
- Build and launch both variants using either of:
mvn clean compile exec:javamvn clean compile exec:java -Palice-and-bobmvn clean compile exec:java -Peve-and-bob
- Verify all commands print an empty board of the initialized game, but only the
alice-and-bob/defaultprofile additionally prints a list of player actions.
Integration tests
- In the last course lecture you've learned that the maven default lifecycle defines two different testing phases:
testfor executing unit tests.verifyfor executing integration tests.
Unit tests vs Integration tests
Unit tests verify the behaviour of individual classes of methods. Integration tests verify the behaviour of component interplay, ensuring the application in its integrity serves its purpose.
Mad Max
One way to integration test game implementations, e.g. the XoxInternals project, is to write robot players (or if you want to use hype language you can call them "AIs").
- Where unit tests verify individual functions of the game, competing robot players attempt to play the game and this way verify if it works reliably.
- The simplest way of such an integration test is the "Mad Max" robot player.
- Mad Max uses a random generator to pick a random action, whenever it is their turn.
- Two competing Mad Max instances must eventually maneuver the game into a game over state.
- The integration test is successful if the game ends without errors within 5 seconds of CPU time.
- A seeded random number generator can be used to ensure deterministic execution paths of the robot players.
- For advanced testing,
1000game instances can be tested, each with an individual seed (e.g. simple incrementing seed value1,2,3,...)
Integration tests are also tests, and belong into the src/test/java folder structure.
- To distinguish between standard unit tests (which must be executed at
testphase) and integration tests (which must be executed in theverifyphase), a special file name syntax can be used. - Integration tests should end with
...IT.java. - An additional
pom.xmlconfiguration for the test plugin then splits execution by file name:<project> ... <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>3.5.2</version> <configuration> <excludes> <exclude>**/*IT.java</exclude> </excludes> </configuration> <executions> <execution> <id>integration-test</id> <goals> <goal>test</goal> </goals> <phase>integration-test</phase> <configuration> <excludes> <exclude>none</exclude> </excludes> <includes> <include>**/*IT.java</include> </includes> </configuration> </execution> </executions> </plugin> </plugins> </build> </project>
Your Turn
- Add the above
maven-surfire-pluginto yourXoxFrontendproject. - Create a new test class
MadMaxIT.java. - In MadMaxIT, create an
@Testdecorated function that runs 1000 different Xox games with two competing MadMax robot players. - Verify the integration tests passes through the 1000 game instances without crashing.
mvn clean testmust not run the integration tests.mvn clean verifymust run the integration tests.
You can use the below code snippets to implement the requested functionality:
/**
* Provides a random number between 0 and the provided value (exclusive).
*
* @param upperBoundExcluded as the maximum value that is guaranteed not to be reached.
*/
public int pickRandomAction(int upperBoundExcluded) {
return Math.abs(randomNumberGenerator.nextInt()) % upperBoundExcluded;
}
/**
* Initialize seeded random number generator. Each game test-run of two competing MadMax players
* should use a different seed.
*/
public void initializeRandomNumberGenerator(int seed) {
// For seed 42, will always generate:
// -1170105035 234785527 -1360544799 205897768 ...
randomNumberGenerator = new Random(seed);
}
/**
* The integration test creates 1000 test games and verifies that each of them is successfully
* finished by two competing MadMax robot players. Every game uses a different random generator
* seed.
*/
@Test
public void madMax() {
// Run 1000 random games. All must conclude in valid game ending
for (int i = 0; i < 1000; i++) {
runSeededRandomGame(i);
}
}
private void runSeededRandomGame(int seed) {
// TODO: implement missing functionality to finish seeded game
long gameId = XoxGameInitiator.createNewGame(playerNames[0], playerNames[1]);
while (!gameOver) {
// select a random move for the current player
XoxClaimFieldAction[] allPossibleActions =
gameManager.getActions(gameId, playerNames[currentPlayer]);
// pick and play the random move
gameManager.performAction(gameId, playerNames[currentPlayer], randomAction);
// advance current player and update game over status
gameOver = gameManager.getRanking(gameId).isGameOver();
}
}