[performance.tests] AT-579. Completion suggested from the version catalog test. Updated driver. Added allure steps

GitOrigin-RevId: 124bbd238da43264e9359d4ed1ded1cb62958ca1
This commit is contained in:
Nikita Barkov
2023-10-17 22:13:31 +02:00
committed by intellij-monorepo-bot
parent a094c4bcb7
commit e49ebf3002
11 changed files with 177 additions and 35 deletions

View File

@@ -4,6 +4,7 @@ import com.intellij.driver.client.Driver
import com.intellij.driver.client.ProjectRef
import com.intellij.driver.client.Remote
import com.intellij.driver.client.Timed
import com.intellij.driver.client.screenshot.TakeScreenshot
import com.intellij.driver.model.LockSemantics
import com.intellij.driver.model.OnDispatcher
import com.intellij.driver.model.ProductVersion
@@ -296,6 +297,10 @@ internal class DriverImpl(host: JmxHost?) : Driver {
return try {
this.code()
}
catch (t: Throwable) {
new(TakeScreenshot::class).takeScreenshot("beforeKill")
throw t
}
finally {
if (currentValue != null) {
sessionHolder.set(currentValue)

View File

@@ -0,0 +1,8 @@
package com.intellij.driver.client.screenshot
import com.intellij.driver.client.Remote
@Remote("com.jetbrains.performancePlugin.commands.TakeScreenshotCommand", plugin = "com.jetbrains.performancePlugin")
interface TakeScreenshot {
fun takeScreenshot(childFolder: String?)
}

View File

@@ -0,0 +1,6 @@
package com.intellij.driver.sdk.commands
import com.intellij.driver.client.Remote
@Remote("com.jetbrains.performancePlugin.CommandLogger", plugin = "com.jetbrains.performancePlugin")
interface CommandLogger

View File

@@ -0,0 +1,8 @@
package com.intellij.driver.sdk.commands
import com.intellij.driver.client.Remote
@Remote("com.jetbrains.performancePlugin.PlaybackRunnerExtended", plugin = "com.jetbrains.performancePlugin")
interface PlaybackRunnerExtended {
fun runBlocking(timeoutsMs: Long = 0)
}

View File

@@ -750,13 +750,23 @@ fun <T : CommandChain> T.showFileHistory(): T {
return this
}
fun <T : CommandChain> T.assertCompletionCommand(): T {
this.addCommand("${CMD_PREFIX}assertCompletionCommand")
fun <T : CommandChain> T.chooseCompletionCommand(completionName: String): T {
this.addCommand("${CMD_PREFIX}chooseCompletionCommand ${completionName}")
return this
}
fun <T : CommandChain> T.assertCompletionCommand(count: Int): T {
this.addCommand("${CMD_PREFIX}assertCompletionCommand ${count}")
fun <T : CommandChain> T.assertCompletionCommand(): T {
this.addCommand("${CMD_PREFIX}assertCompletionCommand EXIST")
return this
}
fun <T : CommandChain> T.assertCompletionCommandContains(completionNames: List<String>): T {
this.addCommand("${CMD_PREFIX}assertCompletionCommand CONTAINS ${completionNames.joinToString(" ")}")
return this
}
fun <T : CommandChain> T.assertCompletionCommandCount(count: Int): T {
this.addCommand("${CMD_PREFIX}assertCompletionCommand COUNT ${count}")
return this
}

View File

@@ -76,6 +76,7 @@ public final class BaseCommandProvider implements CommandProvider {
Map.entry(CollectAllFilesCommand.PREFIX, CollectAllFilesCommand::new),
Map.entry(ExecuteEditorActionCommand.PREFIX, ExecuteEditorActionCommand::new),
Map.entry(AssertCompletionCommand.PREFIX, AssertCompletionCommand::new),
Map.entry(ChooseCompletionCommand.PREFIX, ChooseCompletionCommand::new),
Map.entry(AssertFindUsagesCommand.PREFIX, AssertFindUsagesCommand::new),
Map.entry(SetBreakpointCommand.PREFIX, SetBreakpointCommand::new),
Map.entry(DebugRunConfigurationCommand.PREFIX, DebugRunConfigurationCommand::new),

View File

@@ -9,13 +9,25 @@ import org.jetbrains.annotations.NonNls
import kotlin.io.path.getLastModifiedTime
import kotlin.io.path.listDirectoryEntries
/**
* Command verify last completions. Provides modes: EXIST, COUNT, CONTAINS.
* Example: %assertCompletionCommand EXIST - verify that there was at least one completion item
* Example: %assertCompletionCommand COUNT {NUM} - verify that there were {NUM} completion items
* Example: %assertCompletionCommand CONTAINS {COMPLETION_NAME1} {COMPLETION_NAME2} - verify that completion items contains {COMPLETION_NAME1} {COMPLETION_NAME2}
*/
class AssertCompletionCommand(text: String, line: Int) : PlaybackCommandCoroutineAdapter(text, line) {
companion object {
const val PREFIX: @NonNls String = CMD_PREFIX + "assertCompletionCommand"
private const val EXIST = "EXIST"
private const val COUNT = "COUNT"
private const val CONTAINS = "CONTAINS"
}
override suspend fun doExecute(context: PlaybackContext) {
val completionItemsDir = getCompletionItemsDir()
val commandArgs = extractCommandArgument(PREFIX).split(" ").filterNot { it.trim() == "" }
val commandArgs = extractCommandArgument(PREFIX).split(" ").filterNot { it.trim() == "" }.toMutableList()
val mode = commandArgs.removeAt(0)
if (completionItemsDir == null) {
throw IllegalStateException("Completion items dump dir not set")
}
@@ -27,11 +39,21 @@ class AssertCompletionCommand(text: String, line: Int) : PlaybackCommandCoroutin
if (data.totalNumber <= 0) {
throw IllegalStateException("Expected > 0 completion variants, but got only ${data.totalNumber}")
}
if (commandArgs.isNotEmpty()) {
val expected = commandArgs[0].toInt()
if (data.totalNumber != expected) {
throw IllegalStateException("Expected ${expected} completion variants, but got ${data.totalNumber}")
when (mode) {
EXIST -> return
COUNT -> {
val expected = commandArgs[0].toInt()
if (data.totalNumber != expected) {
throw IllegalStateException("Expected ${expected} completion variants, but got ${data.totalNumber}")
}
}
CONTAINS -> {
val actual = data.items.map { it.name.trim() }
if (!actual.containsAll(commandArgs)) {
throw IllegalStateException("Actual ${actual} does not contain expected ${commandArgs} completion variants")
}
}
else -> throw IllegalArgumentException("Specified mode is neither EXIST nor COUNT nor CONTAINS")
}
}
}

View File

@@ -0,0 +1,62 @@
package com.jetbrains.performancePlugin.commands
import com.intellij.codeInsight.lookup.Lookup
import com.intellij.codeInsight.lookup.LookupManager
import com.intellij.codeInsight.lookup.impl.LookupImpl
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.EDT
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.ui.playback.PlaybackContext
import com.intellij.openapi.ui.playback.commands.PlaybackCommandCoroutineAdapter
import com.jetbrains.performancePlugin.commands.IdeEditorKeyCommand.pressKey
import kotlinx.coroutines.*
import org.jetbrains.annotations.NonNls
import kotlin.time.Duration.Companion.seconds
/**
* Command chooses a completion item by name. Completion popup should be opened.
* Example: %chooseCompletionCommand {COMPLETION_NAME}
*/
class ChooseCompletionCommand(text: String, line: Int) : PlaybackCommandCoroutineAdapter(text, line) {
companion object {
const val PREFIX: @NonNls String = CMD_PREFIX + "chooseCompletionCommand"
}
override suspend fun doExecute(context: PlaybackContext) {
val completionName = extractCommandArgument(PREFIX).trim()
val itemsCount = getLookup(context).items.size
for (i in 1..itemsCount) {
//we need to get lookup every time because otherwise currentItem is not updated
val lookup = getLookup(context)
if (lookup.currentItem?.lookupString != completionName) {
ApplicationManager.getApplication().invokeAndWait {
pressKey(IdeEditorKeyCommand.EditorKey.ARROW_DOWN, context.project)
}
}
else {
withContext(Dispatchers.EDT) {
ApplicationManager.getApplication().invokeAndWait {
lookup.finishLookup(Lookup.NORMAL_SELECT_CHAR)
}
}
return
}
}
throw IllegalArgumentException("There is no completion with name $completionName")
}
private fun getLookup(context: PlaybackContext): LookupImpl {
val editor = FileEditorManager.getInstance(context.project).selectedTextEditor!!
try {
runBlocking {
withTimeout(5.seconds) {
LookupManager.getActiveLookup(editor) as LookupImpl? == null
}
}
}
catch (e: TimeoutCancellationException) {
throw IllegalStateException("There is no lookup after 5 seconds")
}
return LookupManager.getActiveLookup(editor) as LookupImpl
}
}

View File

@@ -8,7 +8,10 @@ import com.intellij.codeInsight.completion.CompletionPhase;
import com.intellij.codeInsight.completion.CompletionPhaseListener;
import com.intellij.codeInsight.completion.CompletionType;
import com.intellij.codeInsight.completion.impl.CompletionServiceImpl;
import com.intellij.codeInsight.lookup.*;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupEx;
import com.intellij.codeInsight.lookup.LookupListener;
import com.intellij.codeInsight.lookup.LookupManager;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
@@ -180,14 +183,14 @@ public class CompletionCommand extends PerformanceCommand {
}
@JsonInclude(JsonInclude.Include.NON_NULL)
private static final class CompletionVariant {
public static final class CompletionVariant {
@JsonProperty
private final String name;
@JsonCreator
private CompletionVariant(@JsonProperty("name") String name) { this.name = name; }
private String getName() {
public String getName() {
return name;
}

View File

@@ -1,6 +1,5 @@
package com.jetbrains.performancePlugin.commands;
import com.intellij.platform.diagnostic.telemetry.helpers.TraceUtil;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.IdeActions;
@@ -14,19 +13,18 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.playback.PlaybackContext;
import com.intellij.openapi.ui.playback.commands.KeyCodeTypeCommand;
import com.intellij.openapi.util.ActionCallback;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.platform.diagnostic.telemetry.helpers.TraceUtil;
import com.jetbrains.performancePlugin.PerformanceTestSpan;
import com.jetbrains.performancePlugin.utils.ActionCallbackProfilerStopper;
import com.jetbrains.performancePlugin.utils.EditorUtils;
import io.opentelemetry.context.Context;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.concurrency.Promise;
import org.jetbrains.concurrency.Promises;
/**
* Command simulates pressing a keyboard key.
* Only defined set of key is supported for now: "ENTER", "BACKSPACE", "TAB" and "ESCAPE"
* Only defined set of key is supported for now: "ENTER", "BACKSPACE", "TAB", "ESCAPE", "ARROW_DOWN" and "ARROW_UP"
* <p>
* Syntax: %pressKey <KEY>
* Example: %pressKey ENTER
@@ -34,7 +32,6 @@ import org.jetbrains.concurrency.Promises;
public class IdeEditorKeyCommand extends KeyCodeTypeCommand {
public static final String PREFIX = CMD_PREFIX + "pressKey";
private String actionID;
public IdeEditorKeyCommand(@NotNull String text, int line) {
super(text, line);
@@ -45,18 +42,19 @@ public class IdeEditorKeyCommand extends KeyCodeTypeCommand {
final ActionCallback actionCallback = new ActionCallbackProfilerStopper();
String input = extractCommandArgument(PREFIX);
switch (StringUtil.toUpperCase(input)) {
case "ENTER" -> actionID = IdeActions.ACTION_EDITOR_ENTER;
case "BACKSPACE" -> actionID = IdeActions.ACTION_EDITOR_BACKSPACE;
case "TAB" -> actionID = IdeActions.ACTION_EDITOR_TAB;
case "ESCAPE" -> actionID = IdeActions.ACTION_EDITOR_ESCAPE;
default -> {
actionCallback.reject("Unknown special character. Please use: ENTER, BACKSPACE, ESCAPE or TAB");
return Promises.toPromise(actionCallback);
}
try {
pressKey(EditorKey.valueOf(input), context.getProject());
actionCallback.setDone();
}
catch (Throwable e) {
actionCallback.reject(e.getMessage());
}
@Nullable Project project = context.getProject();
return Promises.toPromise(actionCallback);
}
public static void pressKey(EditorKey editorKey, Project project) {
String actionID = editorKey.action;
Editor editor = FileEditorManager.getInstance(project).getSelectedTextEditor();
if (editor != null) {
ApplicationManager.getApplication().runWriteAction(Context.current().wrap(() -> {
@@ -70,12 +68,25 @@ public class IdeEditorKeyCommand extends KeyCodeTypeCommand {
});
span.addEvent("Typing " + actionID);
});
actionCallback.setDone();
}));
}
else {
actionCallback.reject("Editor is not opened");
throw new IllegalStateException("Editor is not opened");
}
}
public enum EditorKey {
ENTER(IdeActions.ACTION_EDITOR_ENTER),
BACKSPACE(IdeActions.ACTION_EDITOR_BACKSPACE),
TAB(IdeActions.ACTION_EDITOR_TAB),
ESCAPE(IdeActions.ACTION_EDITOR_ESCAPE),
ARROW_DOWN(IdeActions.ACTION_EDITOR_MOVE_CARET_DOWN),
ARROW_UP(IdeActions.ACTION_EDITOR_MOVE_CARET_PAGE_UP);
private final String action;
EditorKey(String action) {
this.action = action;
}
return Promises.toPromise(actionCallback);
}
}

View File

@@ -10,10 +10,7 @@ import com.intellij.openapi.project.ProjectManager
import com.intellij.openapi.ui.playback.PlaybackContext
import com.intellij.openapi.ui.playback.commands.PlaybackCommandCoroutineAdapter
import com.intellij.util.ui.ImageUtil
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeout
import kotlinx.coroutines.*
import java.awt.*
import java.awt.image.BufferedImage
import java.io.File
@@ -34,6 +31,10 @@ private val LOG: Logger
* Example: %takeScreenshot onExit
</fullPathToFile> */
class TakeScreenshotCommand(text: String, line: Int) : PlaybackCommandCoroutineAdapter(text, line) {
@Suppress("UNUSED") //Needs for Driver
constructor() : this("", 0)
companion object {
const val PREFIX: String = CMD_PREFIX + "takeScreenshot"
}
@@ -41,6 +42,11 @@ class TakeScreenshotCommand(text: String, line: Int) : PlaybackCommandCoroutineA
override suspend fun doExecute(context: PlaybackContext) {
takeScreenshotOfAllWindows(extractCommandArgument(PREFIX).ifEmpty { "beforeExit" })
}
@Suppress("UNUSED") //Needs for Driver
fun takeScreenshot(childFolder: String?) {
runBlocking { takeScreenshotOfAllWindows(childFolder) }
}
}
fun takeScreenshotWithAwtRobot(fullPathToFile: String) {
@@ -69,7 +75,7 @@ fun takeScreenshotWithAwtRobot(fullPathToFile: String) {
}
suspend fun captureComponent(component: Component, file: File) {
if(component.width == 0 || component.height == 0) {
if (component.width == 0 || component.height == 0) {
LOG.info(component.name + " has zero size, skipping")
LOG.info(component.javaClass.toString())
return