/*
* Constellation - An open source and standard compliant SDI
* http://www.constellation-sdi.org
*
* Copyright 2014 Geomatys.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.constellation.ws.embedded;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.sis.util.logging.Logging;
import org.constellation.sql.Result;
import org.constellation.sql.ResultsDatabase;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import static org.constellation.ws.embedded.LaunchTests.CITE_EXECUTABLE_KEY;
/**
* Perform actions on the logs gotten from the execution of {@code Cite tests}.
*
* @version $Id$
*
* @author Cédric Briançon (Geomatys)
* @since 0.4
* @see GrizzlyServer
*/
public final class HandleLogs {
private static final Logger LOGGER = Logging.getLogger("org.constellation.ws.embedded");
/**
* Prevents instantiation.
*/
private HandleLogs() {}
/**
* Inserts the result of the process into the database.
*
* @param in The input stream to display.
* @param service The service name.
* @param version The service version.
* @param date The execution date of the tests suite.
*/
private static void insertResult(final InputStream in, final String service,
final String version, final Date date)
{
ResultsDatabase resDB = null;
try {
final LogParser logParser = new LogParser(date);
final BufferedReader br = new BufferedReader(new InputStreamReader(in));
String line;
Result precedentResult = null;
final List<Result> results = new ArrayList<Result>();
while ((line = br.readLine()) != null) {
if (line.trim().startsWith("Error")) {
throw new IllegalArgumentException("Error the session does not exist.");
}
final Result result = logParser.toResult(line);
if (precedentResult == null) {
result.setGroupNode(true);
} else if (isChildOf(precedentResult, result)) {
precedentResult.setGroupNode(true);
}
precedentResult = result;
results.add(result);
}
br.close();
resDB = new ResultsDatabase();
for (Result result : results) {
resDB.insertResult(result, service, version);
}
resDB.close();
} catch (IOException e) {
// May be normal if we killed the process. Prints only
// a summary of the exception, not the full stack trace.
System.err.println(e);
} catch (SQLException e) {
// May be normal if we killed the process. Prints only
// a summary of the exception, not the full stack trace.
LOGGER.log(Level.WARNING, e.getLocalizedMessage(), e);
} finally {
try {
if (resDB != null) {
resDB.close();
}
} catch (SQLException ex) {
System.err.println(ex);
}
}
}
/**
* return true if the result is a child of the specified precedent result.
* A result id considered as a child if his directory contains the precedent directory.
*
* @param oldResult The precedent result.
* @param result The current result.
*
* @return True if the directory of the current result contain the directory of the precedent result.
*/
private static boolean isChildOf(final Result oldResult, final Result result) {
final String oldDirectory = oldResult.getDirectory();
final String childDirectory = result.getDirectory();
return childDirectory.startsWith(oldDirectory);
}
/**
* Analyzes the result gotten from this session, with the ones of the previous session.
*
* @param date The execution date of the current {@code Cite tests} session.
* @param service The service name.
* @param version The service version.
* @return {@code True} if there is no test that fails for this session and succeed
* for the previous one. {@code False} if there is one or more new problems.
*/
private static boolean analyseResult(final Date date, final String service, final String version) {
ResultsDatabase resDB = null;
try {
resDB = new ResultsDatabase();
return resDB.compareResults(date, service, version);
} catch (SQLException ex) {
// May be normal if we killed the process. Prints only
// a summary of the exception, not the full stack trace.
LOGGER.log(Level.WARNING, ex.getLocalizedMessage(), ex);
return true;
} finally {
try {
if (resDB != null) {
resDB.close();
}
} catch (SQLException ex) {
System.err.println(ex);
}
}
}
/**
* Deletes from the database a specific session that contains new failures
* from the previous one.
*
* @param date The date of the session that will be deleted.
*/
@Deprecated
private static void deleteSessionWithNewFailures(final Date date) {
ResultsDatabase resDB = null;
try {
resDB = new ResultsDatabase();
resDB.deleteSuite(date);
} catch (SQLException ex) {
// May be normal if we killed the process. Prints only
// a summary of the exception, not the full stack trace.
LOGGER.log(Level.WARNING, ex.getLocalizedMessage(), ex);
} finally {
try {
if (resDB != null) {
resDB.close();
}
} catch (SQLException ex) {
System.err.println(ex);
}
}
}
/**
* Update the last session by tagging it as the last succeed suite.
*
* @param date the date of the current session which have succeed
* @param service The service name.
* @param version The service version.
*/
private static void updateSessionSuite(final Date date, final String service, final String version) {
ResultsDatabase resDB = null;
try {
resDB = new ResultsDatabase();
resDB.setSuiteLastSuccess(date, service, version);
} catch (SQLException ex) {
// May be normal if we killed the process. Prints only
// a summary of the exception, not the full stack trace.
LOGGER.log(Level.WARNING, ex.getLocalizedMessage(), ex);
} finally {
try {
if (resDB != null) {
resDB.close();
}
} catch (SQLException ex) {
System.err.println(ex);
}
}
}
/**
* From the logs of a Cite Tests session, extracts various information and
* store them into a database. Those data will be used to compare the rate
* of success between the current session and the previous one.
*
* @param args The session to execute. Each parameters should respect the
* syntax "service-version".
* @throws IOException if the execution of the script fails.
* @throws MojoFailureException if a regression is detected.
*/
public static void main(String[] args) throws IOException, MojoFailureException {
if (args.length == 0) {
System.err.println("No argument have been given to the script. Usage log.sh [profile...]");
return;
}
final Runtime rt = Runtime.getRuntime();
// Stores the date for each sessions in the map.
final Map<String,Date> dateOfSessions = new HashMap<String,Date>();
// Launches the log script, and copy the results into the database.
for (String arg : args) {
if (!arg.contains("-")) {
System.err.println("The session argument should respect the syntax \"service-version\".");
continue;
}
final Date date = new Date();
dateOfSessions.put(arg, date);
final String[] argValue = arg.split("-");
final String service = argValue[0];
final String version = argValue[1];
String basedir = "";
String executable = "../cite/log.sh";
if (System.getProperty(CITE_EXECUTABLE_KEY) != null) {
executable = System.getProperty(CITE_EXECUTABLE_KEY);
if (executable.endsWith("/")) {
basedir = executable;
executable = executable + "log.sh";
} else {
basedir = basedir + '/';
executable = executable + "/log.sh";
}
LOGGER.log(Level.INFO, "using system property specified executable:{0}", executable);
}
final Process process = rt.exec(new String[]{executable, arg, "", basedir});
insertResult(process.getInputStream(), service, version, date);
try {
process.waitFor();
} catch (InterruptedException ex) {
System.err.println(ex);
}
}
/*
* Analyses the results of the session compared to the previous one. If one session
* contains new errors, then an exception will be thrown in the end.
*/
boolean successResults = true;
for (String arg : args) {
final String[] argValue = arg.split("-");
final String service = argValue[0];
final String version = argValue[1];
final Date currentSessionDate = dateOfSessions.get(arg);
if (analyseResult(currentSessionDate, service, version) == false) {
/* The session has already been writen in the database for comparison purpose,
* but in fact we do not want to keep it there because there are new failures.
* We do not want the next tests session to be compared to that one, this way
* it is compulsory to correct newly-failing tests to fix the build.
*/
//deleteSessionWithNewFailures(currentSessionDate);
successResults = false;
} else {
updateSessionSuite(currentSessionDate, service, version);
}
}
if (successResults == false) {
/*
* A regression is detected, a mojo exception is thrown to make the build fail.
*/
throw new MojoFailureException("Some tests are now failing, but not in the previous suite.\n" +
"Please fix service(s) responsible for that building failure.");
}
System.exit(0);
}
}