Skip to content

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 package command will execute all default lifecycle phases until (and including) the package phase.

Your turn

  • You will be working with a new project XoxFrontend, which will call some of the functionality provided by the cloned XoxInternals project.
    • 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=false
    

    If 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:

    XoxFrontend/
    ├── pom.xml
    └── src
        ├── main
           └── java
               └── ca
                   └── uqam
                       └── info
                           └── App.java
        └── test
            └── java
                └── ca
                    └── uqam
                        └── info
                            └── AppTest.java
    
    12 directories, 3 files
    

  • 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 target folder. Verify there are class files, but no jar file.
  • Build the project, by calling the maven default lifecycle phase that does the same as before, but also produces a jar file.
    • Inspect the target folder. Verify there are class files, and a jar file.

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.xml files are an explicit form of declaring dependencies, that is, by inspecting the pom.xml potential 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.xml file 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)

Your turn

  • Open your XoxFrontend project in IntelliJ.
    • Double-click the pom.xml file.
    • 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.
  • 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 .m2 respository.
  • 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:

  • XoxFrontend will be needing functionality from the cloned XoxInternals project.
    • In this case XoxFrontend can declare a <dependency> in it's pom.xml file:
      <dependency>
          <groupId>ca.uqam</groupId>
          <artifactId>xoxinternals</artifactId>
          <version>1.7</version>
      </dependency>
      
    • If we sideload XoxInternals into the local .m2 cache, the dependency is directly:
      • Maven does not need to search on online server for the artifact (we injected it ourselves)
      • XoxFrontend can use the library code without code duplication.

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. XoxInternals has a pom.xml, and is itself a maven project.
  • In this case the install phase can be used, to not only build an artifact, but also sideload it into the local cache.

Your turn

  • Add the above dependency block to the XoxFrontend's pom.xml file.
    • It will show up red. The dependency cannot be resolved, because no such artifact is on the official maven servers.
  • Next: Inspect your local .m2 directory. It may be hidden on your system. If it is not visible in the file manager, open a terminal and access the hidden .m2 directory on your home folder.
    • Inspect the content, verify there is no artifact ca/uqam/info/xoxinternals
  • Build the previously cloned XoxInternals project using the install phase.
  • Inspect your local .m2 cache again. You should not see a new artifact in ca/uqam/info/xoxinternals.
  • Switch back to the XoxFrontend's pom.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 Alice and Bob:

    public class XoxAliceAndBob {
      public static void main(String[] args) {
        long gameId = XoxGameInitiator.createNewGame("Alice", "Bob");
        // Prints actions for alice.
        XoxBoardAndActionPrinter.printBoardAndActionsForPlayer(gameId, "Alice");
      }
    }
    
    For the two missing calls, you can use the below helper classes:

  • 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 .m2 repository cache.
  • In the last course, you've seen a maven command to do just that.

Your turn

  1. Build the XoxInternals project with mvn clean package
  2. Copy the jar file from the target directory to your Desktop.
  3. Delete the XoxInternals project from your hard drive, but keep the jar file.
  4. Delete the XoxInternals artifact from your local .m2 repository 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 XoxFrontend project will no longer work, because we've manually removed the XoxInternals projects it depends on.`

  1. Just using the saved jar file, use the mvn command seen in class to directly install an artifact from a jar file.
  2. Make sure to use the correct groupId, artifactId and version, so the dependency can be correctly resolved.
  3. Verify your local .m2 content and verify that you can now once more build and run the XoxFrontend application.

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 XoxInternals artifact 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 .m2 directory)
  • 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 XoxFrontend project. It will fail, because XoxInternals can no longer be resolved.
  • Access your XoxFrontend project's pom.xml and add a reference the inf2050 repo (which is a non-officiel 3rd party maven repo, created specifically for this course):
    <repositories>
        <repository>
            <id>Max's third party repo on a UQAM GitLab file server</id>
            <url>https://max.pages.info.uqam.ca/inf2050repo/</url>
        </repository>
    </repositories>
    
  • 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 -P switch and the profile name.
    • Example: mvn clean package -Ppremium-software-version

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 Eve and Bob.
      public class XoxEveAndBob {
        public static void main(String[] args) {
          long gameId = XoxGameInitiator.createNewGame("Eve", "Bob");
          // prints nothing! Alice is not participating in match.
          XoxBoardAndActionPrinter.printBoardAndActionsForPlayer(gameId, "Alice");
        }
      }
      
  • Modify the XoxFrontend project's pom.xml, so two new build profiles are defined:
    • alice-and-bob as default profile.
    • eve-and-bob as alternative profile.
  • For each build profile, add a configuration of the exec plugin, make sure that:
    • The alice-and-bob profile launches the original XoxAliceAndBob java launcher class.
    • The eve-and-bob profile launches the newly created XoxEveAndBob java launcher class.
  • Build and launch both variants using either of:
    • mvn clean compile exec:java
    • mvn clean compile exec:java -Palice-and-bob
    • mvn clean compile exec:java -Peve-and-bob
  • Verify all commands print an empty board of the initialized game, but only the alice-and-bob / default profile 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:
    • test for executing unit tests.
    • verify for 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, 1000 game instances can be tested, each with an individual seed (e.g. simple incrementing seed value 1, 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 test phase) and integration tests (which must be executed in the verify phase), a special file name syntax can be used.
  • Integration tests should end with ...IT.java.
  • An additional pom.xml configuration 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

  1. Add the above maven-surfire-plugin to your XoxFrontend project.
  2. Create a new test class MadMaxIT.java.
  3. In MadMaxIT, create an @Test decorated function that runs 1000 different Xox games with two competing MadMax robot players.
  4. Verify the integration tests passes through the 1000 game instances without crashing.
    • mvn clean test must not run the integration tests.
    • mvn clean verify must 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();
  }
}