blob: 7de5fc0d30e008e91bfca5a9b2b65011bc90dca8 [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* 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 com.intellij.openapi.ui.playback;
import com.intellij.ide.IdeEventQueue;
import com.intellij.ide.UiActivityMonitor;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationActivationListener;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.ui.playback.commands.AssertFocused;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.ui.playback.commands.*;
import com.intellij.openapi.ui.playback.commands.ActionCommand;
import com.intellij.openapi.util.ActionCallback;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.wm.IdeFrame;
import com.intellij.util.text.StringTokenizer;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.util.*;
public class PlaybackRunner {
private static final Logger LOG = Logger.getInstance("#com.intellij.ui.debugger.extensions.PlaybackRunner");
private Robot myRobot;
private final String myScript;
private final StatusCallback myCallback;
private final ArrayList<PlaybackCommand> myCommands = new ArrayList<PlaybackCommand>();
private ActionCallback myActionCallback;
private boolean myStopRequested;
private final boolean myUseDirectActionCall;
private boolean myUseTypingTargets;
private File myScriptDir;
private boolean myStopOnAppDeactivation;
private final ApplicationActivationListener myAppListener;
private HashSet<Class> myFacadeClasses = new HashSet<Class>();
private ArrayList<StageInfo> myCurrentStageDepth = new ArrayList<StageInfo>();
private ArrayList<StageInfo> myPassedStages = new ArrayList<StageInfo>();
private long myContextTimestamp;
private Map<String, String> myRegistryValues = new HashMap<String, String>();
private Disposable myOnStop = Disposer.newDisposable();
public PlaybackRunner(String script, StatusCallback callback, final boolean useDirectActionCall, boolean stopOnAppDeactivation, boolean useTypingTargets) {
myScript = script;
myCallback = callback;
myUseDirectActionCall = useDirectActionCall;
myUseTypingTargets = useTypingTargets;
myStopOnAppDeactivation = stopOnAppDeactivation;
myAppListener = new ApplicationActivationListener.Adapter() {
@Override
public void applicationDeactivated(IdeFrame ideFrame) {
if (myStopOnAppDeactivation) {
myCallback.message(null, "App lost focus, stopping...", StatusCallback.Type.message);
stop();
}
}
};
}
public ActionCallback run() {
myStopRequested = false;
myRegistryValues.clear();
final UiActivityMonitor activityMonitor = UiActivityMonitor.getInstance();
activityMonitor.clear();
activityMonitor.setActive(true);
myCurrentStageDepth.clear();
myPassedStages.clear();
myContextTimestamp++;
ApplicationManager.getApplication().getMessageBus().connect(myOnStop).subscribe(ApplicationActivationListener.TOPIC, myAppListener);
try {
myActionCallback = new ActionCallback();
myActionCallback.doWhenProcessed(new Runnable() {
@Override
public void run() {
stop();
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
activityMonitor.setActive(false);
restoreRegistryValues();
}
});
}
});
myRobot = new Robot();
parse();
new Thread() {
@Override
public void run() {
if (myUseDirectActionCall) {
executeFrom(0, getScriptDir());
}
else {
IdeEventQueue.getInstance().doWhenReady(new Runnable() {
public void run() {
executeFrom(0, getScriptDir());
}
});
}
}
}.start();
}
catch (AWTException e) {
LOG.error(e);
}
return myActionCallback;
}
private void restoreRegistryValues() {
final Set<String> storedKeys = myRegistryValues.keySet();
for (String each : storedKeys) {
Registry.get(each).setValue(myRegistryValues.get(each));
}
}
private void executeFrom(final int cmdIndex, File baseDir) {
if (cmdIndex < myCommands.size()) {
final PlaybackCommand cmd = myCommands.get(cmdIndex);
if (myStopRequested) {
myCallback.message(null, "Stopped", StatusCallback.Type.message);
myActionCallback.setRejected();
return;
}
final PlaybackContext context =
new PlaybackContext(this, myCallback, cmdIndex, myRobot, myUseDirectActionCall, myUseTypingTargets, cmd, baseDir, (Set<Class>)myFacadeClasses.clone()) {
private long myTimeStamp = myContextTimestamp;
public void pushStage(StageInfo info) {
myCurrentStageDepth.add(info);
}
public StageInfo popStage() {
if (myCurrentStageDepth.size() > 0) {
return myCurrentStageDepth.remove(myCurrentStageDepth.size() - 1);
}
return null;
}
public int getCurrentStageDepth() {
return myCurrentStageDepth.size();
}
@Override
public void addPassed(StageInfo stage) {
myPassedStages.add(stage);
}
@Override
public boolean isDisposed() {
return myTimeStamp != myContextTimestamp;
}
@Override
public void storeRegistryValue(String key) {
if (!myRegistryValues.containsKey(key)) {
myRegistryValues.put(key, Registry.stringValue(key));
}
}
};
final ActionCallback cmdCallback = cmd.execute(context);
cmdCallback.doWhenDone(new Runnable() {
public void run() {
if (cmd.canGoFurther()) {
executeFrom(cmdIndex + 1, context.getBaseDir());
}
else {
myCallback.message(null, "Stopped", StatusCallback.Type.message);
myActionCallback.setDone();
}
}
}).doWhenRejected(new Runnable() {
public void run() {
myCallback.message(null, "Stopped", StatusCallback.Type.message);
myActionCallback.setRejected();
}
});
}
else {
myCallback.message(null, "Finished OK " + myPassedStages.size() + " tests", StatusCallback.Type.message);
myActionCallback.setDone();
}
}
private void parse() {
includeScript(myScript, getScriptDir(), myCommands, 0);
}
private void includeScript(String scriptText, File scriptDir, ArrayList<PlaybackCommand> commandList, int line) {
final StringTokenizer tokens = new StringTokenizer(scriptText, "\n");
while (tokens.hasMoreTokens()) {
final String eachLine = tokens.nextToken();
String includeCmd = AbstractCommand.CMD_PREFIX + "include";
String importCallCmd = AbstractCommand.CMD_PREFIX + "importCall";
if (eachLine.startsWith(includeCmd)) {
File file = new PathMacro().setScriptDir(scriptDir).resolveFile(eachLine.substring(includeCmd.length()).trim(), scriptDir);
if (!file.exists()) {
commandList.add(new ErrorCommand("Cannot find file to include: " + file.getAbsolutePath(), line));
return;
}
try {
String include = FileUtil.loadFile(file);
myCommands.add(new PrintCommand(eachLine, line));
includeScript(include, file.getParentFile(), commandList, 0);
}
catch (IOException e) {
commandList.add(new ErrorCommand("Error reading file: " + file.getAbsolutePath(), line));
return;
}
}
else if (eachLine.startsWith(importCallCmd)) {
String className = eachLine.substring(importCallCmd.length()).trim();
try {
Class<?> facadeClass = Class.forName(className);
myFacadeClasses.add(facadeClass);
myCommands.add(new PrintCommand(eachLine, line++));
}
catch (ClassNotFoundException e) {
commandList.add(new ErrorCommand("Cannot find class: " + className, line));
return;
}
}
else {
final PlaybackCommand cmd = createCommand(eachLine, line++, scriptDir);
commandList.add(cmd);
}
}
}
protected PlaybackCommand createCommand(String string, int line, File scriptDir) {
AbstractCommand cmd;
if (string.startsWith(RegistryValueCommand.PREFIX)) {
cmd = new RegistryValueCommand(string, line);
} else if (string.startsWith(AbstractCommand.CMD_PREFIX + AbstractCommand.CMD_PREFIX)) {
cmd = new EmptyCommand(line);
}
else if (string.startsWith(KeyCodeTypeCommand.PREFIX)) {
cmd = new KeyCodeTypeCommand(string, line);
}
else if (string.startsWith(DelayCommand.PREFIX)) {
cmd = new DelayCommand(string, line);
}
else if (string.startsWith(KeyShortcutCommand.PREFIX)) {
cmd = new KeyShortcutCommand(string, line);
}
else if (string.startsWith(ActionCommand.PREFIX)) {
cmd = new ActionCommand(string, line);
}
else if (string.startsWith(ToggleActionCommand.PREFIX)) {
cmd = new ToggleActionCommand(string, line);
}
else if (string.startsWith(StopCommand.PREFIX)) {
cmd = new StopCommand(string, line);
}
else if (string.startsWith(AssertFocused.PREFIX)) {
return new AssertFocused(string, line);
}
else if (string.startsWith(CallCommand.PREFIX)) {
cmd = new CallCommand(string, line);
}
else if (string.startsWith(CdCommand.PREFIX)) {
cmd = new CdCommand(string, line);
}
else if (string.startsWith(PushStage.PREFIX)) {
cmd = new PushStage(string, line);
}
else if (string.startsWith(PopStage.PREFIX)) {
cmd = new PopStage(string, line);
}
else {
cmd = new AlphaNumericTypeCommand(string, line);
}
cmd.setScriptDir(scriptDir);
return cmd;
}
private void setDone() {
myActionCallback.setDone();
}
public void stop() {
myStopRequested = true;
Disposer.dispose(myOnStop);
}
public File getScriptDir() {
return myScriptDir != null ? myScriptDir : new File(System.getProperty("user.dir"));
}
public void setScriptDir(File baseDir) {
myScriptDir = baseDir;
}
public interface StatusCallback {
enum Type {message, error, code, test}
void message(@Nullable PlaybackContext context, String text, Type type);
abstract class Edt implements StatusCallback {
public final void message(final PlaybackContext context,
final String text,
final Type type) {
if (SwingUtilities.isEventDispatchThread()) {
messageEdt(context, text, type);
}
else {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
messageEdt(context, text, type);
}
});
}
}
public abstract void messageEdt(@Nullable PlaybackContext context, String text, Type type);
}
}
}