/*
* 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 net.grinder.engine.agent;
import net.grinder.common.GrinderProperties;
import net.grinder.communication.FanOutStreamSender;
import net.grinder.engine.common.ScriptLocation;
import net.grinder.lang.AbstractLanguageHandler;
import net.grinder.lang.Lang;
import net.grinder.util.AbstractGrinderClassPathProcessor;
import net.grinder.util.Directory;
import net.grinder.util.NetworkUtils;
import net.grinder.util.thread.Condition;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.util.Properties;
import static org.ngrinder.common.util.NoOp.noOp;
/**
* Script validation service.
*
* It works on local instead of remote agent. The reason this class is located
* in ngrinder-core is... some The Grinder core class doesn't have public
* access..
*
* @author JunHo Yoon
* @since 3.0
*/
public class LocalScriptTestDriveService {
private static final Logger LOGGER = LoggerFactory.getLogger(LocalScriptTestDriveService.class);
public static final int DEFAULT_TIMEOUT = 100;
/**
* Validate script with 100 sec timeout.
*
* @param base working directory
* @param script script file
* @param eventSynchronisation condition for event synchronization
* @param securityEnabled if security is set or not.
* @param hostString hostString
* @return File which stores validation result.
*/
public File doValidate(File base, File script, Condition eventSynchronisation, boolean securityEnabled,
String hostString) {
return doValidate(base, script, eventSynchronisation, securityEnabled, hostString, getDefaultTimeout());
}
protected int getDefaultTimeout() {
return DEFAULT_TIMEOUT;
}
/**
* Validate script.
*
* @param base working directory
* @param script script file
* @param eventSynchronisation condition for event synchronization
* @param securityEnabled if security is set or not.
* @param hostString hostString
* @param timeout timeout in sec.
* @return File which stores validation result.
*/
@SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter")
public File doValidate(File base, File script, Condition eventSynchronisation, boolean securityEnabled,
String hostString, final int timeout) {
FanOutStreamSender fanOutStreamSender = null;
ErrorStreamRedirectWorkerLauncher workerLauncher = null;
boolean stopByTooMuchExecution = false;
ByteArrayOutputStream byteArrayErrorStream = new ByteArrayOutputStream();
File file = new File(base, "validation-0.log");
try {
fanOutStreamSender = new FanOutStreamSender(1);
deleteLogs(base);
AbstractLanguageHandler handler = Lang.getByFileName(script).getHandler();
AbstractGrinderClassPathProcessor classPathProcessor = handler.getClassPathProcessor();
GrinderProperties properties = new GrinderProperties();
PropertyBuilder builder = new PropertyBuilder(properties, new Directory(base), securityEnabled, hostString,
NetworkUtils.getLocalHostName());
properties.setInt("grinder.agents", 1);
properties.setInt("grinder.processes", 1);
properties.setInt("grinder.threads", 1);
properties.setBoolean("grinder.script.validation", true);
String grinderJVMClassPath = classPathProcessor.buildForemostClasspathBasedOnCurrentClassLoader(LOGGER)
+ File.pathSeparator + classPathProcessor.buildPatchClasspathBasedOnCurrentClassLoader(LOGGER)
+ File.pathSeparator + builder.buildCustomClassPath(true);
properties.setProperty("grinder.jvm.classpath", grinderJVMClassPath);
LOGGER.info("grinder.jvm.classpath : {} ", grinderJVMClassPath);
AgentIdentityImplementation agentIdentity = new AgentIdentityImplementation("validation");
agentIdentity.setNumber(0);
String newClassPath = classPathProcessor.buildClasspathBasedOnCurrentClassLoader(LOGGER);
LOGGER.debug("validation class path " + newClassPath);
Properties systemProperties = new Properties();
systemProperties.put("java.class.path", base.getAbsolutePath() + File.pathSeparator + newClassPath);
Directory workingDirectory = new Directory(base);
String buildJVMArgumentWithoutMemory = builder.buildJVMArgumentWithoutMemory();
LOGGER.info("jvm args : {} ", buildJVMArgumentWithoutMemory);
final WorkerProcessCommandLine workerCommandLine = new WorkerProcessCommandLine(properties,
systemProperties, buildJVMArgumentWithoutMemory, workingDirectory);
ScriptLocation scriptLocation = new ScriptLocation(workingDirectory, script);
ProcessWorkerFactory workerFactory = new ProcessWorkerFactory(workerCommandLine, agentIdentity,
fanOutStreamSender, false, scriptLocation, properties);
workerLauncher = new ErrorStreamRedirectWorkerLauncher(1, workerFactory, eventSynchronisation, LOGGER,
byteArrayErrorStream);
// Start
workerLauncher.startAllWorkers();
// Wait for a termination event.
synchronized (eventSynchronisation) {
final long sleep = 1000;
int waitingCount = 0;
while (true) {
if (workerLauncher.allFinished()) {
break;
}
if (waitingCount++ > timeout) {
LOGGER.error("Validation should be performed within {} sec. Stop it by force", timeout);
workerLauncher.destroyAllWorkers();
stopByTooMuchExecution = true;
break;
}
eventSynchronisation.waitNoInterrruptException(sleep);
}
}
} catch (Exception e) {
LOGGER.error("Error while executing {} because {}", script, e.getMessage());
LOGGER.info("The error detail is ", e);
appendingMessageOn(file, ExceptionUtils.getFullStackTrace(e));
} finally {
if (workerLauncher != null) {
workerLauncher.shutdown();
}
if (fanOutStreamSender != null) {
fanOutStreamSender.shutdown();
}
// To be safe, wait again..
int waitingCount = 0;
while (workerLauncher != null) {
final int maximumWaitingCount = 10;
if (workerLauncher.allFinished() || waitingCount++ > maximumWaitingCount) {
break;
}
synchronized (eventSynchronisation) {
eventSynchronisation.waitNoInterrruptException(1000);
}
}
}
appendingMessageOn(file, byteArrayErrorStream.toString());
File errorValidation = new File(base, "error_validation-0.log");
if (errorValidation.exists()) {
String errorValidationResult = "";
try {
errorValidationResult = FileUtils.readFileToString(errorValidation);
} catch (IOException e) {
noOp();
}
appendingMessageOn(file, errorValidationResult);
}
if (stopByTooMuchExecution) {
appendingMessageOn(file, "Validation should be performed within " + timeout + " sec. Stop it by force");
}
return file;
}
@SuppressWarnings("ResultOfMethodCallIgnored")
private void deleteLogs(File base) {
base.listFiles(new FileFilter() {
@Override
public boolean accept(File pathName) {
String extension = FilenameUtils.getExtension(pathName.getName());
if (extension.startsWith("log")) {
pathName.delete();
}
return true;
}
});
}
private void appendingMessageOn(File file, String msg) {
FileWriter fileWriter = null;
try {
fileWriter = new FileWriter(file, true);
fileWriter.append("\n\n").append(msg);
} catch (IOException e) {
LOGGER.error("Error during appending validation messages", e);
} finally {
IOUtils.closeQuietly(fileWriter);
}
}
}