fll.web.FullTournamentTest.java Source code

Java tutorial

Introduction

Here is the source code for fll.web.FullTournamentTest.java

Source

/*
 * Copyright (c) 2000-2003 INSciTE.  All rights reserved
 * INSciTE is on the web at: http://www.hightechkids.org
 * This code is released under GPL; see LICENSE.txt for details.
 */
package fll.web;

import static org.hamcrest.core.IsNot.not;
import static org.hamcrest.core.IsNull.notNullValue;
import static org.hamcrest.core.StringContains.containsString;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;

import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.ParseException;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipInputStream;

import javax.swing.table.TableModel;

import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.openqa.selenium.Alert;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.Select;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;

import com.gargoylesoftware.htmlunit.Page;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.WebRequest;
import com.gargoylesoftware.htmlunit.html.HtmlCheckBoxInput;
import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.html.HtmlOption;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.gargoylesoftware.htmlunit.html.HtmlSelect;
import com.gargoylesoftware.htmlunit.html.HtmlSubmitInput;

import au.com.bytecode.opencsv.CSVWriter;
import fll.JudgeInformation;
import fll.TestUtils;
import fll.Tournament;
import fll.TournamentTeam;
import fll.Utilities;
import fll.db.CategoryColumnMapping;
import fll.db.GlobalParameters;
import fll.db.ImportDB;
import fll.db.Queries;
import fll.db.TournamentParameters;
import fll.scheduler.TournamentSchedule;
import fll.subjective.SubjectiveFrame;
import fll.util.LogUtils;
import fll.web.developer.QueryHandler;
import fll.web.scoreEntry.ScoreEntry;
import fll.xml.AbstractGoal;
import fll.xml.ChallengeDescription;
import fll.xml.Goal;
import fll.xml.PerformanceScoreCategory;
import fll.xml.ScoreCategory;

/**
 * Test a full tournament.
 */
public class FullTournamentTest {

    private static final Logger LOGGER = LogUtils.getLogger();

    private WebDriver selenium;

    public void setUp(final IntegrationTestUtils.WebDriverType driver) {
        selenium = IntegrationTestUtils.createWebDriver(driver);
    }

    /**
     * Uses the firefox driver.
     * 
     * @throws Exception
     */
    @Before
    public void setUp() throws Exception {
        LogUtils.initializeLogging();
        setUp(IntegrationTestUtils.WebDriverType.FIREFOX);
    }

    @After
    public void tearDown() {
        if (null != selenium) {
            selenium.quit();
        }
    }

    /**
     * Load the test data into the specified database.
     */
    private static void loadTestData(final Connection testDataConn) throws SQLException, IOException {
        try (final InputStream dbResourceStream = FullTournamentTest.class
                .getResourceAsStream("data/99-final.flldb")) {
            Assert.assertNotNull("Missing test data", dbResourceStream);
            final ZipInputStream zipStream = new ZipInputStream(dbResourceStream);
            ImportDB.loadFromDumpIntoNewDB(zipStream, testDataConn);
        }
    }

    /**
     * Test a full tournament. This tests to make sure everything works normally.
     * 
     * @throws MalformedURLException
     * @throws IOException
     * @throws ClassNotFoundException
     * @throws InstantiationException
     * @throws IllegalAccessException
     * @throws ParseException
     * @throws SQLException
     * @throws InterruptedException
     * @throws SAXException
     */
    @Test
    public void testFullTournament() throws IOException, ClassNotFoundException, InstantiationException,
            IllegalAccessException, ParseException, SQLException, InterruptedException, SAXException {

        try {
            Class.forName("org.hsqldb.jdbcDriver").newInstance();

            try (final Connection testDataConn = DriverManager
                    .getConnection("jdbc:hsqldb:mem:full-tournament-test")) {
                Assert.assertNotNull("Error connecting to test data database", testDataConn);

                loadTestData(testDataConn);

                final String testTournamentName = "Field";

                final Path outputDirectory = Files.createDirectories(Paths.get("FullTournamentTestOutputs"));

                if (null != outputDirectory) {
                    // make sure the directory exists
                    Files.createDirectories(outputDirectory);
                }

                replayTournament(testDataConn, testTournamentName, outputDirectory);

                LOGGER.info("Computing final scores");
                computeFinalScores();

                LOGGER.info("Checking the reports");
                checkReports();

                LOGGER.info("Checking rank and scores");
                checkRankAndScores(testTournamentName);
            } // try Connection

        } catch (final AssertionError e) {
            IntegrationTestUtils.storeScreenshot(selenium);
            throw e;
        } catch (final RuntimeException e) {
            IntegrationTestUtils.storeScreenshot(selenium);
            throw e;
        } catch (final IOException e) {
            IntegrationTestUtils.storeScreenshot(selenium);
            throw e;
        } catch (final ClassNotFoundException e) {
            IntegrationTestUtils.storeScreenshot(selenium);
            throw e;
        } catch (final InstantiationException e) {
            IntegrationTestUtils.storeScreenshot(selenium);
            throw e;
        } catch (final IllegalAccessException e) {
            IntegrationTestUtils.storeScreenshot(selenium);
            throw e;
        } catch (final ParseException e) {
            IntegrationTestUtils.storeScreenshot(selenium);
            throw e;
        } catch (final SQLException e) {
            IntegrationTestUtils.storeScreenshot(selenium);
            throw e;
        } catch (final InterruptedException e) {
            IntegrationTestUtils.storeScreenshot(selenium);
            throw e;
        }
    }

    /**
     * Replay a tournament.
     * 
     * @param testDataConn connection to the source data
     * @param testTournamentName name of the tournament to create
     * @param outputDirectory where to save files, must not be null and must exist
     * @throws IOException
     * @throws SQLException
     * @throws ParseException
     * @throws InterruptedException
     * @throws SAXException
     */
    public void replayTournament(final Connection testDataConn, final String testTournamentName,
            final Path outputDirectory)
            throws IOException, SQLException, ParseException, InterruptedException, SAXException {

        final String safeTestTournamentName = sanitizeFilename(testTournamentName);

        final Document challengeDocument = GlobalParameters.getChallengeDocument(testDataConn);
        Assert.assertNotNull(challengeDocument);

        assertThat(outputDirectory, notNullValue());
        assertTrue("Output directory must exist", Files.exists(outputDirectory));

        final Tournament sourceTournament = Tournament.findTournamentByName(testDataConn, testTournamentName);
        Assert.assertNotNull(sourceTournament);

        final int numSeedingRounds = TournamentParameters.getNumSeedingRounds(testDataConn,
                sourceTournament.getTournamentID());

        // --- initialize database ---
        LOGGER.info("Initializing the database");
        IntegrationTestUtils.initializeDatabase(selenium, challengeDocument);

        LOGGER.info("Loading teams");
        loadTeams(testDataConn, sourceTournament, outputDirectory);

        IntegrationTestUtils.downloadFile(new URL(TestUtils.URL_ROOT + "admin/database.flldb"), "application/zip",
                outputDirectory.resolve(safeTestTournamentName + "_01-teams-loaded.flldb"));

        LOGGER.info("Setting current tournament");
        IntegrationTestUtils.setTournament(selenium, sourceTournament.getName());

        LOGGER.info("Loading the schedule");
        uploadSchedule(testDataConn, sourceTournament, outputDirectory);
        IntegrationTestUtils.downloadFile(new URL(TestUtils.URL_ROOT + "admin/database.flldb"), "application/zip",
                outputDirectory.resolve(safeTestTournamentName + "_02-schedule-loaded.flldb"));

        LOGGER.info("Assigning judges");
        assignJudges(testDataConn, sourceTournament);

        IntegrationTestUtils.downloadFile(new URL(TestUtils.URL_ROOT + "admin/database.flldb"), "application/zip",
                outputDirectory.resolve(safeTestTournamentName + "_03-judges-assigned.flldb"));

        LOGGER.info("Assigning table labels");
        assignTableLabels();

        IntegrationTestUtils.downloadFile(new URL(TestUtils.URL_ROOT + "admin/database.flldb"), "application/zip",
                outputDirectory.resolve(safeTestTournamentName + "_04-table-labels-assigned.flldb"));

        /*
         * --- Enter 3 runs for each team --- Use data from test data base,
         * converted from Field 2005. Enter 4th run and rest of playoffs.
         */
        final int maxRuns;
        try (final PreparedStatement maxRunPrep = testDataConn
                .prepareStatement("SELECT MAX(RunNumber) FROM Performance WHERE Tournament = ?")) {
            maxRunPrep.setInt(1, sourceTournament.getTournamentID());
            try (final ResultSet maxRunResult = maxRunPrep.executeQuery()) {
                Assert.assertTrue("No performance scores in test data", maxRunResult.next());
                maxRuns = maxRunResult.getInt(1);
            }
        }

        final ChallengeDescription description = new ChallengeDescription(challengeDocument.getDocumentElement());
        final PerformanceScoreCategory performanceElement = description.getPerformance();

        try (final PreparedStatement prep = testDataConn
                .prepareStatement("SELECT TeamNumber FROM Performance WHERE Tournament = ? AND RunNumber = ?")) {

            boolean initializedPlayoff = false;
            prep.setInt(1, sourceTournament.getTournamentID());

            final Set<String> awardGroups = getAwardGroups();
            for (int runNumber = 1; runNumber <= maxRuns; ++runNumber) {

                if (runNumber > numSeedingRounds) {
                    if (!initializedPlayoff) {
                        IntegrationTestUtils.downloadFile(new URL(TestUtils.URL_ROOT + "admin/database.flldb"),
                                "application/zip", outputDirectory
                                        .resolve(safeTestTournamentName + "_05-seeding-rounds-completed.flldb"));

                        checkSeedingRounds();

                        // initialize the playoff brackets with playoff/index.jsp form
                        for (final String awardGroup : awardGroups) {
                            LOGGER.info("Initializing playoff brackets for division " + awardGroup);
                            IntegrationTestUtils.initializePlayoffsForAwardGroup(selenium, awardGroup);
                        }
                        initializedPlayoff = true;
                    }
                }

                prep.setInt(2, runNumber);
                try (final ResultSet rs = prep.executeQuery()) {
                    // for each score in a run
                    while (rs.next()) {
                        final int teamNumber = rs.getInt(1);
                        enterPerformanceScore(testDataConn, performanceElement, sourceTournament, runNumber,
                                teamNumber);

                        // give the web server a chance to catch up
                        Thread.sleep(IntegrationTestUtils.WAIT_FOR_PAGE_LOAD_MS);

                        verifyPerformanceScore(testDataConn, performanceElement, sourceTournament, runNumber,
                                teamNumber);
                    }
                }

                if (runNumber > numSeedingRounds && runNumber != maxRuns) {
                    for (final String division : awardGroups) {
                        printPlayoffScoresheets(division);
                    }
                }

            }

            LOGGER.info("Checking displays");
            checkDisplays();

            LOGGER.info("Checking the subjective scores");
            enterSubjectiveScores(testDataConn, description, sourceTournament, outputDirectory);

            LOGGER.info("Writing final datbaase");
            IntegrationTestUtils.downloadFile(new URL(TestUtils.URL_ROOT + "admin/database.flldb"),
                    "application/zip", outputDirectory.resolve(safeTestTournamentName + "_99-final.flldb"));
        }

    }

    /**
     * @param testDataConn
     * @param sourceTournament
     * @throws SQLException
     * @throws IOException
     * @throws InterruptedException
     */
    private void uploadSchedule(final Connection testDataConn, final Tournament sourceTournament,
            final Path outputDirectory) throws SQLException, IOException, InterruptedException {
        if (TournamentSchedule.scheduleExistsInDatabase(testDataConn, sourceTournament.getTournamentID())) {

            final TournamentSchedule schedule = new TournamentSchedule(testDataConn,
                    sourceTournament.getTournamentID());

            final Path outputFile = outputDirectory
                    .resolve(sanitizeFilename(sourceTournament.getName()) + "_schedule.csv");
            schedule.writeToCSV(outputFile.toFile());

            // upload the saved file
            IntegrationTestUtils.loadPage(selenium, TestUtils.URL_ROOT + "admin/index.jsp");
            final WebElement fileInput = selenium.findElement(By.name("scheduleFile"));
            fileInput.sendKeys(outputFile.toAbsolutePath().toString());
            selenium.findElement(By.id("upload-schedule")).click();
            Assert.assertFalse(IntegrationTestUtils.isElementPresent(selenium, By.id("error")));

            // check that we're on the choose headers page and set the header
            // mappings
            Assert.assertTrue(selenium.getCurrentUrl().contains("chooseSubjectiveHeaders"));
            final Collection<CategoryColumnMapping> mappings = CategoryColumnMapping.load(testDataConn,
                    sourceTournament.getTournamentID());
            for (final CategoryColumnMapping map : mappings) {
                final Select select = new Select(selenium.findElement(By.name(map.getCategoryName() + ":header")));
                select.selectByVisibleText(map.getScheduleColumn());
            }
            selenium.findElement(By.id("submit")).click();

            Thread.sleep(IntegrationTestUtils.WAIT_FOR_PAGE_LOAD_MS);

            // check that we don't have hard violations and skip past soft
            // violations
            assertThat(selenium.getCurrentUrl(), not(containsString("displayHardViolations")));
            if (selenium.getCurrentUrl().contains("displaySoftViolations")) {
                selenium.findElement(By.id("yes")).click();

                Thread.sleep(IntegrationTestUtils.WAIT_FOR_PAGE_LOAD_MS);
            }

            // set event divisions
            if (selenium.getCurrentUrl().contains("promptForEventDivision")) {
                selenium.findElement(By.id("yes")).click();

                Thread.sleep(IntegrationTestUtils.WAIT_FOR_PAGE_LOAD_MS);

                // assume the values are fine
                assertThat(selenium.getCurrentUrl(), containsString("displayEventDivisionConfirmation"));
                selenium.findElement(By.id("yes")).click();

                Thread.sleep(IntegrationTestUtils.WAIT_FOR_PAGE_LOAD_MS);
            }

            // check that it all worked
            Assert.assertFalse(IntegrationTestUtils.isElementPresent(selenium, By.id("error")));
            Assert.assertTrue(IntegrationTestUtils.isElementPresent(selenium, By.id("success")));
        }
    }

    /**
     * Make sure there are no teams with more or less than seeding rounds.
     * @throws InterruptedException 
     */
    private void checkSeedingRounds() throws IOException, InterruptedException {
        IntegrationTestUtils.loadPage(selenium, TestUtils.URL_ROOT + "playoff");
        selenium.findElement(By.id("check_seeding_rounds")).click();

        Assert.assertFalse("Some teams with more or less than seeding rounds found",
                IntegrationTestUtils.isElementPresent(selenium, By.className("warning")));
    }

    /**
     * Get the award groups in this tournament.
     * 
     * @throws IOException
     * @throws InterruptedException 
     */
    private Set<String> getAwardGroups() throws IOException, InterruptedException {
        IntegrationTestUtils.loadPage(selenium, TestUtils.URL_ROOT + "admin/index.jsp");

        selenium.findElement(By.id("change-award-groups")).click();

        final Set<String> awardGroups = new HashSet<>();
        final List<WebElement> inputs = selenium.findElements(By.cssSelector("input:checked[type='radio']"));
        for (final WebElement radioButton : inputs) {
            final String awardGroup = radioButton.getAttribute("value");
            awardGroups.add(awardGroup);
        }

        LOGGER.info("Found awardGroups: " + awardGroups);

        Assert.assertFalse(awardGroups.isEmpty());
        return awardGroups;
    }

    private void assignTableLabels() throws IOException, InterruptedException {
        IntegrationTestUtils.loadPage(selenium, TestUtils.URL_ROOT + "admin/tables.jsp");

        final WebElement sidea0 = selenium.findElement(By.name("SideA0"));
        final WebElement sideb0 = selenium.findElement(By.name("SideB0"));
        if (StringUtils.isEmpty(sidea0.getAttribute("value"))
                && StringUtils.isEmpty(sideb0.getAttribute("value"))) {
            // Table labels should be assigned by the schedule, but may not be. If
            // they're not assigned, then assign them.
            sidea0.sendKeys("red");
            sideb0.sendKeys("blue");
        }

        selenium.findElement(By.id("finished")).click();

        Assert.assertFalse(IntegrationTestUtils.isElementPresent(selenium, By.id("error")));
        Assert.assertTrue(IntegrationTestUtils.isElementPresent(selenium, By.id("success")));
    }

    private void assignJudge(final String id, final String category, final String station, final int judgeIndex)
            throws IOException, InterruptedException {
        if (LOGGER.isTraceEnabled()) {
            LOGGER.trace("Assigning judge '" + id + "' cat: '" + category + "' station: '" + station + "' index: "
                    + judgeIndex);
        }

        // make sure the row exists
        while (!IntegrationTestUtils.isElementPresent(selenium, By.name("id" + String.valueOf(judgeIndex)))) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Adding a row to the judges entry form to get to: " + judgeIndex);
                IntegrationTestUtils.storeScreenshot(selenium);
            }
            selenium.findElement(By.id("add_rows")).click();

            // let the javascript do it's work
            Thread.sleep(IntegrationTestUtils.WAIT_FOR_PAGE_LOAD_MS);
        }

        selenium.findElement(By.name("id" + judgeIndex)).sendKeys(id);

        final Select categorySelect = new Select(selenium.findElement(By.name("cat" + judgeIndex)));
        categorySelect.selectByValue(category);

        final Select stationSelect = new Select(selenium.findElement(By.name("station" + judgeIndex)));
        stationSelect.selectByValue(station);

    }

    private void assignJudges(final Connection testDataConn, final Tournament sourceTournament)
            throws IOException, SQLException, InterruptedException {

        IntegrationTestUtils.loadPage(selenium, TestUtils.URL_ROOT + "admin/index.jsp");
        selenium.findElement(By.id("assign_judges")).click();

        final Collection<JudgeInformation> judges = JudgeInformation.getJudges(testDataConn,
                sourceTournament.getTournamentID());

        int judgeIndex = 1;
        for (final JudgeInformation judge : judges) {

            assignJudge(judge.getId(), judge.getCategory(), judge.getGroup(), judgeIndex);

            ++judgeIndex;
        }

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("After assigning judges");
            IntegrationTestUtils.storeScreenshot(selenium);
        }

        // submit those values
        selenium.findElement(By.id("finished")).click();

        Assert.assertFalse("Got error from judges assignment",
                IntegrationTestUtils.isElementPresent(selenium, By.id("error")));

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Verifying judges");
            IntegrationTestUtils.storeScreenshot(selenium);
        }

        // commit judges information
        selenium.findElement(By.id("commit")).click();
        Assert.assertTrue("Error assigning judges",
                IntegrationTestUtils.isElementPresent(selenium, By.id("success")));

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("After committing judges");
            IntegrationTestUtils.storeScreenshot(selenium);
        }

    }

    /**
     * Load the teams from testDataConnection.
     * 
     * @param testDataConnection where to get the teams from
     * @param outputDirectory where to write the teams file, may be null in which
     *          case a temp file will be used
     * @throws IOException
     * @throws SQLException
     * @throws InterruptedException 
     */
    private void loadTeams(final Connection testDataConnection, final Tournament sourceTournament,
            final Path outputDirectory) throws IOException, SQLException, InterruptedException {

        final Path teamsFile = outputDirectory.resolve(sanitizeFilename(sourceTournament.getName()) + "_teams.csv");
        // write the teams out to a file
        try (final Writer writer = new FileWriter(teamsFile.toFile())) {
            try (final CSVWriter csvWriter = new CSVWriter(writer)) {
                csvWriter.writeNext(new String[] { "team_name", "team_number", "affiliation", "award_group",
                        "judging_group", "tournament" });
                final Map<Integer, TournamentTeam> sourceTeams = Queries.getTournamentTeams(testDataConnection,
                        sourceTournament.getTournamentID());
                for (final Map.Entry<Integer, TournamentTeam> entry : sourceTeams.entrySet()) {
                    final TournamentTeam team = entry.getValue();

                    csvWriter.writeNext(new String[] { team.getTeamName(), Integer.toString(team.getTeamNumber()),
                            team.getOrganization(), team.getAwardGroup(), team.getJudgingGroup(),
                            sourceTournament.getName() });
                }
            }
        }

        IntegrationTestUtils.loadPage(selenium, TestUtils.URL_ROOT + "admin/");

        selenium.findElement(By.id("teams_file")).sendKeys(teamsFile.toAbsolutePath().toString());

        selenium.findElement(By.id("upload_teams")).click();

        IntegrationTestUtils.assertNoException(selenium);

        // skip past the filter page
        selenium.findElement(By.id("next")).click();
        IntegrationTestUtils.assertNoException(selenium);

        // team column selection
        new Select(selenium.findElement(By.name("TeamNumber"))).selectByValue("team_number");
        new Select(selenium.findElement(By.name("TeamName"))).selectByValue("team_name");
        new Select(selenium.findElement(By.name("Organization"))).selectByValue("affiliation");
        new Select(selenium.findElement(By.name("tournament"))).selectByValue("tournament");
        new Select(selenium.findElement(By.name("event_division"))).selectByValue("award_group");
        new Select(selenium.findElement(By.name("judging_station"))).selectByValue("judging_group");
        selenium.findElement(By.id("next")).click();
        IntegrationTestUtils.assertNoException(selenium);
        Assert.assertTrue(IntegrationTestUtils.isElementPresent(selenium, By.id("success")));
    }

    private void computeFinalScores() throws IOException, InterruptedException {
        // compute final scores
        IntegrationTestUtils.loadPage(selenium, TestUtils.URL_ROOT + "report/summarizePhase1.jsp");

        selenium.findElement(By.id("continue")).click();

        Assert.assertTrue(IntegrationTestUtils.isElementPresent(selenium, By.id("success")));
    }

    private void checkReports() throws IOException, SAXException, InterruptedException {
        // generate reports

        IntegrationTestUtils.loadPage(selenium, TestUtils.URL_ROOT + "report/CategorizedScores");

        IntegrationTestUtils.loadPage(selenium, TestUtils.URL_ROOT + "report/CategoryScoresByJudge");

        IntegrationTestUtils.downloadFile(new URL(TestUtils.URL_ROOT + "report/FinalComputedScores"),
                "application/pdf", null);

        IntegrationTestUtils.downloadFile(new URL(TestUtils.URL_ROOT + "report/CategoryScoresByScoreGroup"),
                "application/pdf", null);

        IntegrationTestUtils.downloadFile(new URL(TestUtils.URL_ROOT + "report/PlayoffReport"), "application/pdf",
                null);
    }

    private void checkRankAndScores(final String testTournamentName) throws IOException, SAXException {
        // check ranking and scores
        final double scoreFP = 1E-1; // just check to one decimal place

        final String sqlTemplate = "SELECT FinalScores.TeamNumber, FinalScores.OverallScore" //
                + " FROM FinalScores, current_tournament_teams, Tournaments" //
                + " WHERE FinalScores.TeamNumber = current_tournament_teams.TeamNumber" //
                + " AND Tournaments.Name = '%s'" + " AND FinalScores.Tournament = Tournaments.tournament_id" //
                + " AND current_tournament_teams.event_division = '%s'" //
                + " ORDER BY FinalScores.OverallScore DESC";

        // division 1
        final int[] division1ExpectedRank = { 2636, 3127, 3439, 4462, 3125, 2116, 2104, 2113 };
        final double[] division1ExpectedScores = { 472.76, 423.58, 411.04, 378.04, 374.86, 346.63, 325.95, 310.61 };
        String division = "DivI/Gr4-6";

        final String div1Query = String.format(sqlTemplate, testTournamentName, division);
        final QueryHandler.ResultData div1Result = WebTestUtils.executeServerQuery(div1Query);

        int rank = 0;
        for (final Map<String, String> row : div1Result.getData()) {
            if (LOGGER.isTraceEnabled()) {
                LOGGER.trace("checkRankAndScores - row: " + row);
            }

            final int teamNumber = Integer.parseInt(row.get("teamnumber"));
            Assert.assertEquals("Division I Ranking is incorrect for rank: " + rank, division1ExpectedRank[rank],
                    teamNumber);
            final double score = Double.valueOf(row.get("overallscore"));
            Assert.assertEquals("Overall score incorrect for team: " + teamNumber, division1ExpectedScores[rank],
                    score, scoreFP);

            ++rank;
        }

        // division 2
        final int[] division2ExpectedRank = { 3208, 3061, 2863, 2110, 3063, 353, 3129, 2043 };
        final double[] division2ExpectedScores = { 546.78, 512.05, 426.02, 410.23, 407.15, 355.42, 350.14, 348.75 };
        division = "DivII/Gr7-9";

        final String div2Query = String.format(sqlTemplate, testTournamentName, division);
        final QueryHandler.ResultData div2Result = WebTestUtils.executeServerQuery(div2Query);

        rank = 0;
        for (final Map<String, String> row : div2Result.getData()) {
            final int teamNumber = Integer.parseInt(row.get("teamnumber"));
            Assert.assertEquals("Division II Ranking is incorrect for rank: " + rank, division2ExpectedRank[rank],
                    teamNumber);
            final double score = Double.valueOf(row.get("overallscore"));
            Assert.assertEquals("Overall score incorrect for team: " + teamNumber, division2ExpectedScores[rank],
                    score, scoreFP);

            ++rank;
        }
    }

    /**
     * Visit the printable brackets for the division specified and print the
     * brackets.
     * 
     * @throws IOException
     * @throws MalformedURLException
     * @throws InterruptedException
     * @throws SAXException
     */
    private static void printPlayoffScoresheets(final String division)
            throws MalformedURLException, IOException, InterruptedException, SAXException {
        final WebClient conversation = WebTestUtils.getConversation();

        final Page indexResponse = WebTestUtils.loadPage(conversation,
                new WebRequest(new URL(TestUtils.URL_ROOT + "playoff/index.jsp")));
        Assert.assertTrue(indexResponse.isHtmlPage());
        final HtmlPage indexHtml = (HtmlPage) indexResponse;

        // find form named 'printable'
        HtmlForm form = indexHtml.getFormByName("printable");
        Assert.assertNotNull("printable form not found", form);

        final String formSource = WebTestUtils.getPageSource(form.getPage());
        LOGGER.debug("Form source: " + formSource);

        // set division
        final HtmlSelect divisionSelect = indexHtml.getHtmlElementById("printable.division");
        final HtmlOption divisionOption = divisionSelect.getOptionByValue(division);
        divisionSelect.setSelectedAttribute(divisionOption, true);

        // click 'Display Brackets'
        final HtmlSubmitInput displayBrackets = form.getInputByValue("Display Brackets");
        final com.gargoylesoftware.htmlunit.WebRequest displayBracketsRequest = form.getWebRequest(displayBrackets);
        final Page displayResponse = WebTestUtils.loadPage(conversation, displayBracketsRequest);

        Assert.assertTrue(displayResponse.isHtmlPage());
        final HtmlPage displayHtml = (HtmlPage) displayResponse;

        // find form named 'printScoreSheets'
        form = displayHtml.getFormByName("printScoreSheets");
        Assert.assertNotNull("printScoreSheets form not found", form);

        final HtmlCheckBoxInput printCheck = form.getInputByName("print1");
        printCheck.setChecked(true);

        // click 'Print scoresheets'
        final HtmlSubmitInput print = form.getInputByValue("Print scoresheets");
        final com.gargoylesoftware.htmlunit.WebRequest printRequest = form.getWebRequest(print);
        final Page printResponse = WebTestUtils.loadPage(conversation, printRequest);

        // check that result is PDF
        Assert.assertEquals("application/pdf", printResponse.getWebResponse().getContentType());

    }

    /**
     * Simulate entering subjective scores by pulling them out of testDataConn.
     * 
     * @param testDataConn Where to get the test data from
     * @param challengeDocument the challenge descriptor
     * @throws SQLException
     * @throws SAXException
     * @throws InterruptedException 
     */
    private void enterSubjectiveScores(final Connection testDataConn, final ChallengeDescription description,
            final Tournament sourceTournament, final Path outputDirectory) throws SQLException, IOException,
            MalformedURLException, ParseException, SAXException, InterruptedException {

        final Path subjectiveZip = outputDirectory
                .resolve(sanitizeFilename(sourceTournament.getName()) + "_subjective-data.fll");

        IntegrationTestUtils.downloadFile(new URL(TestUtils.URL_ROOT + "admin/subjective-data.fll"),
                "application/zip", subjectiveZip);

        final SubjectiveFrame subjective = new SubjectiveFrame();
        subjective.load(subjectiveZip.toFile());

        // insert scores into zip
        for (final ScoreCategory subjectiveElement : description.getSubjectiveCategories()) {
            final String category = subjectiveElement.getName();
            final String title = subjectiveElement.getTitle();

            // find appropriate table model
            final TableModel tableModel = subjective.getTableModelForTitle(title);
            Assert.assertNotNull(tableModel);

            final int teamNumberColumn = findColumnByName(tableModel, "TeamNumber");
            Assert.assertTrue("Can't find TeamNumber column in subjective table model", teamNumberColumn >= 0);
            if (LOGGER.isTraceEnabled()) {
                LOGGER.trace("Found team number column at " + teamNumberColumn);
            }

            try (final PreparedStatement prep = testDataConn
                    .prepareStatement("SELECT * FROM " + category + " WHERE Tournament = ?")) {
                prep.setInt(1, sourceTournament.getTournamentID());

                try (final ResultSet rs = prep.executeQuery()) {
                    while (rs.next()) {
                        final int teamNumber = rs.getInt("TeamNumber");

                        // find row number in table
                        int rowIndex = -1;
                        for (int rowIdx = 0; rowIdx < tableModel.getRowCount(); ++rowIdx) {
                            final Object teamNumberRaw = tableModel.getValueAt(rowIdx, teamNumberColumn);
                            Assert.assertNotNull(teamNumberRaw);
                            final int value = Utilities.NUMBER_FORMAT_INSTANCE.parse(teamNumberRaw.toString())
                                    .intValue();

                            if (LOGGER.isTraceEnabled()) {
                                LOGGER.trace("Checking if " + teamNumber + " equals " + value + " raw: "
                                        + teamNumberRaw + "? " + (value == teamNumber) + " rowIdx: " + rowIdx
                                        + " numRows: " + tableModel.getRowCount());
                            }

                            if (value == teamNumber) {
                                rowIndex = rowIdx;
                                break;
                            }
                        }
                        Assert.assertTrue("Can't find team " + teamNumber + " in subjective table model",
                                rowIndex >= 0);

                        if (rs.getBoolean("NoShow")) {
                            // find column for no show
                            final int columnIndex = findColumnByName(tableModel, "No Show");
                            Assert.assertTrue("Can't find No Show column in subjective table model",
                                    columnIndex >= 0);
                            tableModel.setValueAt(Boolean.TRUE, rowIndex, columnIndex);
                        } else {
                            for (final AbstractGoal goalElement : subjectiveElement.getGoals()) {
                                if (!goalElement.isComputed()) {
                                    final String goalName = goalElement.getName();
                                    final String goalTitle = goalElement.getTitle();

                                    // find column index for goal and call set
                                    final int columnIndex = findColumnByName(tableModel, goalTitle);
                                    Assert.assertTrue(
                                            "Can't find " + goalTitle + " column in subjective table model",
                                            columnIndex >= 0);
                                    final int value = rs.getInt(goalName);
                                    tableModel.setValueAt(Integer.valueOf(value), rowIndex, columnIndex);
                                }
                            }
                        } // not NoShow
                    } // foreach score
                } // try ResultSet
            } // try PreparedStatement
        } // foreach category
        subjective.save();

        // upload scores
        IntegrationTestUtils.loadPage(selenium, TestUtils.URL_ROOT + "admin/index.jsp");
        final WebElement fileInput = selenium.findElement(By.name("subjectiveFile"));
        fileInput.sendKeys(subjectiveZip.toAbsolutePath().toString());

        selenium.findElement(By.id("uploadSubjectiveFile")).click();

        Assert.assertFalse(IntegrationTestUtils.isElementPresent(selenium, By.id("error")));
        Assert.assertTrue(IntegrationTestUtils.isElementPresent(selenium, By.id("success")));

    }

    /**
     * Enter a teams performance score. Data is pulled from testDataConn and
     * pushed to the website.
     * 
     * @throws InterruptedException
     */
    private void enterPerformanceScore(final Connection testDataConn,
            final PerformanceScoreCategory performanceElement, final Tournament sourceTournament,
            final int runNumber, final int teamNumber)
            throws SQLException, IOException, MalformedURLException, ParseException, InterruptedException {

        if (LOGGER.isInfoEnabled()) {
            LOGGER.info("Setting score for " + teamNumber + " run: " + runNumber);
        }

        try (final PreparedStatement prep = testDataConn.prepareStatement(
                "SELECT * FROM Performance WHERE Tournament = ? AND RunNumber = ? AND TeamNumber = ?")) {
            prep.setInt(1, sourceTournament.getTournamentID());
            prep.setInt(2, runNumber);
            prep.setInt(3, teamNumber);

            try (final ResultSet rs = prep.executeQuery()) {
                if (rs.next()) {
                    if (rs.getBoolean("BYE")) {
                        LOGGER.info("Run is a bye, not entering a score");
                        return;
                    }

                    // need to get the score entry form
                    IntegrationTestUtils.loadPage(selenium, TestUtils.URL_ROOT + "scoreEntry/select_team.jsp");

                    // select this entry
                    new Select(selenium.findElement(By.id("select-teamnumber")))
                            .selectByValue(String.valueOf(teamNumber));

                    // submit the page
                    selenium.findElement(By.id("enter_submit")).click();

                    Assert.assertFalse("Errors: ",
                            IntegrationTestUtils.isElementPresent(selenium, By.name("error")));

                    if (rs.getBoolean("NoShow")) {
                        selenium.findElement(By.id("no_show")).click();
                    } else {
                        // walk over challenge descriptor to get all element names and then
                        // use the values from rs
                        for (final AbstractGoal element : performanceElement.getGoals()) {
                            if (!element.isComputed()) {
                                final Goal goal = (Goal) element;
                                final String name = goal.getName();
                                final double min = goal.getMin();
                                final double max = goal.getMax();
                                if (LOGGER.isDebugEnabled()) {
                                    LOGGER.debug(
                                            "Setting form parameter: " + name + " min: " + min + " max: " + max);
                                }

                                if (goal.isEnumerated()) {
                                    final String valueStr = rs.getString(name);
                                    final String radioID = ScoreEntry.getIDForEnumRadio(name, valueStr);
                                    selenium.findElement(By.id(radioID)).click();
                                } else if (goal.isYesNo()) {
                                    final int value = rs.getInt(name);
                                    final String buttonID;
                                    if (0 == value) {
                                        buttonID = name + "_no";
                                    } else {
                                        buttonID = name + "_yes";
                                    }
                                    selenium.findElement(By.id(buttonID)).click();
                                } else {
                                    final int initialValue = (int) goal.getInitialValue();
                                    final int value = rs.getInt(name);
                                    final String buttonID;
                                    final int difference;
                                    if (initialValue < value) {
                                        // increment
                                        difference = value - initialValue;
                                        buttonID = ScoreEntry.getIncDecButtonID(name, 1);
                                    } else if (value < initialValue) {
                                        // decrement
                                        difference = initialValue - value;
                                        buttonID = ScoreEntry.getIncDecButtonID(name, -1);
                                    } else {
                                        // no change
                                        difference = 0;
                                        buttonID = null;
                                    }
                                    for (int i = 0; i < difference; ++i) {
                                        selenium.findElement(By.id(buttonID)).click();
                                    }

                                }
                            } // !computed
                        } // foreach goal

                        // check that the submit button is active
                        assertTrue("Submit button is not enabled, invalid score entered",
                                selenium.findElement(By.id("submit")).isEnabled());

                        selenium.findElement(By.id("submit")).click();
                    } // not NoShow

                    Thread.sleep(50);

                    final Alert confirmScoreChange = selenium.switchTo().alert();
                    if (LOGGER.isTraceEnabled()) {
                        LOGGER.trace("Confirmation text: " + confirmScoreChange.getText());
                    }
                    confirmScoreChange.accept();

                    Assert.assertFalse("Errors: ",
                            IntegrationTestUtils.isElementPresent(selenium, By.name("error")));
                } else {
                    Assert.fail("Cannot find scores for " + teamNumber + " run " + runNumber);
                }
            } // try ResultSet
        } // try PreparedStatement

    }

    /**
     * Enter a teams performance score. Data is pulled from testDataConn and
     * pushed to the website.
     * 
     * @throws InterruptedException
     */
    private void verifyPerformanceScore(final Connection testDataConn,
            final PerformanceScoreCategory performanceElement, final Tournament sourceTournament,
            final int runNumber, final int teamNumber)
            throws SQLException, IOException, MalformedURLException, ParseException, InterruptedException {
        final String selectTeamPage = TestUtils.URL_ROOT + "scoreEntry/select_team.jsp";

        if (LOGGER.isInfoEnabled()) {
            LOGGER.info("Verify score for " + teamNumber + " run: " + runNumber);
        }

        try (final PreparedStatement prep = testDataConn.prepareStatement(
                "SELECT * FROM Performance WHERE Tournament = ? AND RunNumber = ? AND TeamNumber = ?")) {
            prep.setInt(1, sourceTournament.getTournamentID());
            prep.setInt(2, runNumber);
            prep.setInt(3, teamNumber);

            try (final ResultSet rs = prep.executeQuery()) {
                if (rs.next()) {
                    if (rs.getBoolean("NoShow")) {
                        // no shows don't need verifying
                        return;
                    } else {
                        // need to get the score entry form
                        IntegrationTestUtils.loadPage(selenium, selectTeamPage);

                        new Select(selenium.findElement(By.id("select-verify-teamnumber")))
                                .selectByValue(teamNumber + "-" + runNumber);

                        // submit the page
                        selenium.findElement(By.id("verify_submit")).click();
                        Thread.sleep(IntegrationTestUtils.WAIT_FOR_PAGE_LOAD_MS);

                        // walk over challenge descriptor to get all element names and then
                        // use the values from rs
                        for (final AbstractGoal element : performanceElement.getGoals()) {
                            if (!element.isComputed()) {
                                final Goal goal = (Goal) element;
                                final String name = goal.getName();

                                if (goal.isEnumerated()) {
                                    // need check if the right radio button is selected
                                    final String value = rs.getString(name);

                                    final String formValue = selenium
                                            .findElement(By.name(ScoreEntry.getElementNameForYesNoDisplay(name)))
                                            .getAttribute("value");
                                    Assert.assertNotNull("Null value for goal: " + name, formValue);

                                    Assert.assertEquals("Wrong enum selected for goal: " + name,
                                            value.toLowerCase(), formValue.toLowerCase());
                                } else if (goal.isYesNo()) {
                                    final String formValue = selenium
                                            .findElement(By.name(ScoreEntry.getElementNameForYesNoDisplay(name)))
                                            .getAttribute("value");
                                    Assert.assertNotNull("Null value for goal: " + name, formValue);

                                    // yes/no
                                    final int value = rs.getInt(name);
                                    final String expectedValue;
                                    if (value == 0) {
                                        expectedValue = "no";
                                    } else {
                                        expectedValue = "yes";
                                    }
                                    Assert.assertEquals("Wrong value for goal: " + name,
                                            expectedValue.toLowerCase(), formValue.toLowerCase());
                                } else {
                                    final String formValue = selenium.findElement(By.name(name))
                                            .getAttribute("value");
                                    Assert.assertNotNull("Null value for goal: " + name, formValue);

                                    final int value = rs.getInt(name);
                                    final int formValueInt = Integer.parseInt(formValue);
                                    Assert.assertEquals("Wrong value for goal: " + name, value, formValueInt);
                                }
                            } // !computed
                        } // foreach goal

                        // Set the verified field to yes
                        selenium.findElement(By.id("Verified_yes")).click();

                        // submit score
                        selenium.findElement(By.id("submit")).click();
                    } // not NoShow

                    Thread.sleep(IntegrationTestUtils.WAIT_FOR_PAGE_LOAD_MS);

                    LOGGER.debug("Checking for an alert");

                    // confirm selection, not going to bother checking the text
                    final Alert confirmScoreChange = selenium.switchTo().alert();
                    if (LOGGER.isTraceEnabled()) {
                        LOGGER.trace("Confirmation text: " + confirmScoreChange.getText());
                    }
                    confirmScoreChange.accept();

                    // give the web server a chance to catch up
                    Thread.sleep(IntegrationTestUtils.WAIT_FOR_PAGE_LOAD_MS);

                    // check for errors
                    // Gives trouble too often
                    // Assert.assertEquals(selectTeamPage, selenium.getCurrentUrl());
                    Assert.assertTrue(
                            "Error submitting form, not on select team page url: " + selenium.getCurrentUrl(),
                            selenium.getPageSource().contains("Unverified Runs"));

                } else {
                    Assert.fail("Cannot find scores for " + teamNumber + " run " + runNumber);
                }
            } // try ResultSet
        } // try PreparedStatement

    }

    /**
     * Check display pages that aren't shown otherwise.
     * 
     * @throws IOException
     * @throws InterruptedException 
     */
    private void checkDisplays() throws IOException, InterruptedException {
        IntegrationTestUtils.loadPage(selenium, TestUtils.URL_ROOT + "scoreboard/main.jsp");

        IntegrationTestUtils.loadPage(selenium, TestUtils.URL_ROOT + "playoff/remoteMain.jsp");

        IntegrationTestUtils.loadPage(selenium, TestUtils.URL_ROOT + "welcome.jsp");
    }

    /**
     * Find a column index in a table model by name.
     * 
     * @return -1 if not found
     */
    private static int findColumnByName(final TableModel tableModel, final String name) {
        for (int i = 0; i < tableModel.getColumnCount(); ++i) {
            if (name.equals(tableModel.getColumnName(i))) {
                return i;
            }
        }
        return -1;
    }

    /**
     * Create a string that's a valid file name.
     */
    private static String sanitizeFilename(final String str) {
        if (null == str || "".equals(str)) {
            return "NULL";
        } else {
            String ret = str;
            final Matcher illegalCharMatcher = ILLEGAL_CHAR_PATTERN.matcher(ret);
            ret = illegalCharMatcher.replaceAll("_");

            return ret;
        }
    }

    private static final Pattern ILLEGAL_CHAR_PATTERN = Pattern.compile("[^A-Za-z0-9_]");

}