diff --git a/.github/workflows/release_and_pulbish_plugin_on_tag.yml b/.github/workflows/release_and_pulbish_plugin_on_tag.yml index 3ac0bb8..2cc6975 100644 --- a/.github/workflows/release_and_pulbish_plugin_on_tag.yml +++ b/.github/workflows/release_and_pulbish_plugin_on_tag.yml @@ -15,7 +15,7 @@ jobs: needs: [check-gradle-wrapper] strategy: matrix: - platform-version: [ 203, 211 ] + platform-version: [ 211, 212 ] env: ORG_GRADLE_PROJECT_platformVersion: ${{ matrix.platform-version }} ORG_GRADLE_PROJECT_publishChannel: stable diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 650204b..da41dec 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -26,7 +26,7 @@ jobs: strategy: fail-fast: false matrix: - platform-version: [ 203, 211 ] + platform-version: [ 211, 212 ] timeout-minutes: 60 env: ORG_GRADLE_PROJECT_platformVersion: ${{ matrix.platform-version }} @@ -70,7 +70,7 @@ jobs: strategy: fail-fast: false matrix: - platform-version: [ 203, 211 ] + platform-version: [ 211, 212 ] timeout-minutes: 60 env: ORG_GRADLE_PROJECT_platformVersion: ${{ matrix.platform-version }} diff --git a/build.gradle.kts b/build.gradle.kts index 518793b..c0e3619 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,13 +3,13 @@ * found in the LICENSE file. */ -import org.gradle.api.JavaVersion.VERSION_1_8 +import org.gradle.api.JavaVersion.VERSION_11 import org.gradle.api.internal.HasConvention import org.intellij.markdown.ast.getTextInNode import org.jetbrains.grammarkit.tasks.GenerateLexer import org.jetbrains.grammarkit.tasks.GenerateParser import org.jetbrains.intellij.tasks.RunIdeTask -import org.jetbrains.intellij.tasks.PublishTask +import org.jetbrains.intellij.tasks.PublishPluginTask import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet import org.jetbrains.kotlin.gradle.tasks.KotlinCompile @@ -40,15 +40,15 @@ buildscript { idea { module { // https://github.com/gradle/kotlin-dsl/issues/537/ - excludeDirs = excludeDirs + file("testData") + file("deps") + excludeDirs = excludeDirs + file("testData") } } plugins { idea - kotlin("jvm") version "1.4.32" - id("org.jetbrains.intellij") version "0.7.3" - id("org.jetbrains.grammarkit") version "2021.1.2" + kotlin("jvm") version "1.6.0" + id("org.jetbrains.intellij") version "1.2.1" + id("org.jetbrains.grammarkit") version "2021.1.3" id("net.saliman.properties") version "1.5.1" } @@ -63,7 +63,6 @@ allprojects { repositories { mavenCentral() - jcenter() maven("https://cache-redirector.jetbrains.com/intellij-dependencies") } @@ -87,8 +86,8 @@ allprojects { } intellij { - version = baseVersion - sandboxDirectory = "$buildDir/$baseIDE-sandbox-$platformVersion" + version.set(baseVersion) + sandboxDir.set("$buildDir/$baseIDE-sandbox-$platformVersion") } sourceSets { @@ -103,25 +102,23 @@ allprojects { } } - configure { - sourceCompatibility = VERSION_1_8 - targetCompatibility = VERSION_1_8 + configure { + sourceCompatibility = VERSION_11 + targetCompatibility = VERSION_11 } tasks { withType { kotlinOptions { - jvmTarget = "1.8" - languageVersion = "1.3" - apiVersion = "1.3" + jvmTarget = "11" freeCompilerArgs = listOf("-Xjvm-default=enable") } } withType { - sinceBuild(prop("sinceBuild")) - untilBuild(prop("untilBuild")) - changeNotes(getLastReleaseNotes()) + sinceBuild.set(prop("sinceBuild")) + untilBuild.set(prop("untilBuild")) + changeNotes.set(provider {getLastReleaseNotes()}) // to check } withType { @@ -152,16 +149,16 @@ val pluginVersion = prop("pluginVersion") project(":plugin"){ version = "$pluginVersion$versionSuffix" intellij { - pluginName = "opa-idea-plugin" - val plugins = mutableListOf( + pluginName.set("opa-idea-plugin") + val pluginList = mutableListOf( "PsiViewer:$psiViewerPluginVersion" ) if (baseIDE == "idea") { - plugins += listOf( + pluginList += listOf( "java" ) } - setPlugins(*plugins.toTypedArray()) + plugins.set(pluginList) } dependencies{ @@ -178,12 +175,12 @@ project(":plugin"){ withType { jvmArgs("--add-exports", "java.base/jdk.internal.vm=ALL-UNNAMED") } - withType { - token(prop("publishToken")) - channels(channel) + withType { + token.set(prop("publishToken")) + channels.set(listOf(channel)) } runPluginVerifier { - ideVersions(prop("pluginVerifierIdeVersions")) + ideVersions.set(prop("pluginVerifierIdeVersions").split(',').map(String::trim).filter(String::isNotEmpty)) } buildSearchableOptions { // buildSearchableOptions task doesn't make sense for non-root subprojects @@ -225,8 +222,8 @@ project(":") { doLast { rootProject.allprojects .map { it.configurations } - .flatMap { listOf(it.compile, it.testCompile) } - .forEach { it.get().resolve() } + .flatMap { it.filter { c -> c.isCanBeResolved } } + .forEach { it.resolve() } } } } @@ -286,4 +283,4 @@ fun getLastReleaseNotes(changLogPath: String = "CHANGELOG.md"): String { releaseNotesChildren ) return org.intellij.markdown.html.HtmlGenerator(src, root, flavour).generateHtml() -} \ No newline at end of file +} diff --git a/gradle-211.properties b/gradle-211.properties index 4fc48cd..5cb86bf 100644 --- a/gradle-211.properties +++ b/gradle-211.properties @@ -10,7 +10,7 @@ psiViewerPluginVersion=211-SNAPSHOT # see https://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/build_number_ranges.html for more information sinceBuild=211.6693 -untilBuild=212.* +untilBuild=211.* # check the binary compatibility of the plugin against these idea versions ( more info at https://github.com/JetBrains/gradle-intellij-plugin#plugin-verifier-dsl) pluginVerifierIdeVersions= IC-2021.1 diff --git a/gradle-203.properties b/gradle-212.properties similarity index 51% rename from gradle-203.properties rename to gradle-212.properties index 3947deb..e68fc86 100644 --- a/gradle-203.properties +++ b/gradle-212.properties @@ -4,13 +4,13 @@ # # if you change the version of ide, also change psiViewerPluginVersion accordingly (cf https://plugins.jetbrains.com/plugin/227-psiviewer/versions) -ideaVersion=IC-2020.3.4 -pycharmCommunityVersion=PC-2020.3.4 -psiViewerPluginVersion=203-SNAPSHOT +ideaVersion=IC-2021.2.3 +pycharmCommunityVersion=PC-2021.2.3 +psiViewerPluginVersion=212-SNAPSHOT # see https://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/build_number_ranges.html for more information -sinceBuild=203.7148 -untilBuild=203.* +sinceBuild=212.4746 +untilBuild=213.* -# # check the binary compatibility of the plugin against these idea versions ( more info at https://github.com/JetBrains/gradle-intellij-plugin#plugin-verifier-dsl) -pluginVerifierIdeVersions= IC-2020.3.4 +# check the binary compatibility of the plugin against these idea versions ( more info at https://github.com/JetBrains/gradle-intellij-plugin#plugin-verifier-dsl) +pluginVerifierIdeVersions= IC-2021.2, IC-2021.3 diff --git a/gradle.properties b/gradle.properties index 0307a04..a617a68 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,8 +9,8 @@ kotlin.code.style=official # gradle property file named gradle-${environmentName}.properties propertiesPluginEnvironmentNameProperty=platformVersion -# Supported platforms: 203, 211 -platformVersion=211 +# Supported platforms: 211, 212 +platformVersion=212 # Version of the ide used for the sandbox( possible value are 'idea' or 'pycharmCommunity') @@ -25,3 +25,8 @@ publishChannel=dev pluginVersion=nextVersion enableBuildSearchableOptions=false + +# Opt-out flag for bundling Kotlin standard library. +# See https://plugins.jetbrains.com/docs/intellij/kotlin.html#kotlin-standard-library for details. +# suppress inspection "UnusedProperty" +kotlin.stdlib.default.dependency = false \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 87b738c..490fda8 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 859cc55..ffed3a2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,11 +1,5 @@ -# -# Use of this source code is governed by the MIT license that can be -# found in the LICENSE file. -# - -#Wed Mar 11 22:19:20 CET 2020 -distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index af6708f..2fe81a7 100755 --- a/gradlew +++ b/gradlew @@ -1,5 +1,21 @@ #!/usr/bin/env sh +# +# Copyright 2015 the original author or authors. +# +# 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 +# +# https://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. +# + ############################################################################## ## ## Gradle start up script for UN*X @@ -28,7 +44,7 @@ APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m"' +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" @@ -109,8 +125,8 @@ if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` @@ -138,19 +154,19 @@ if $cygwin ; then else eval `echo args$i`="\"$arg\"" fi - i=$((i+1)) + i=`expr $i + 1` done case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi @@ -159,14 +175,9 @@ save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } -APP_ARGS=$(save "$@") +APP_ARGS=`save "$@"` # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi - exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 0f8d593..9109989 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,3 +1,19 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @@ -13,8 +29,11 @@ if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome diff --git a/src/main/kotlin/org/openpolicyagent/ideaplugin/ide/actions/utils.kt b/src/211/main/kotlin/org/openpolicyagent/ideaplugin/ide/actions/utils.kt similarity index 100% rename from src/main/kotlin/org/openpolicyagent/ideaplugin/ide/actions/utils.kt rename to src/211/main/kotlin/org/openpolicyagent/ideaplugin/ide/actions/utils.kt diff --git a/src/main/kotlin/org/openpolicyagent/ideaplugin/ide/runconfig/test/go/time.kt b/src/211/main/kotlin/org/openpolicyagent/ideaplugin/ide/runconfig/test/go/time.kt similarity index 100% rename from src/main/kotlin/org/openpolicyagent/ideaplugin/ide/runconfig/test/go/time.kt rename to src/211/main/kotlin/org/openpolicyagent/ideaplugin/ide/runconfig/test/go/time.kt diff --git a/src/211/test/kotlin/org/openpolicyagent/ideaplugin/OpaTestBase.kt b/src/211/test/kotlin/org/openpolicyagent/ideaplugin/OpaTestBase.kt new file mode 100644 index 0000000..7611b21 --- /dev/null +++ b/src/211/test/kotlin/org/openpolicyagent/ideaplugin/OpaTestBase.kt @@ -0,0 +1,36 @@ +/* + * Use of this source code is governed by the MIT license that can be + * found in the LICENSE file. + */ + +package org.openpolicyagent.ideaplugin + +import com.intellij.testFramework.fixtures.BasePlatformTestCase +import org.openpolicyagent.ideaplugin.FileTree +import org.openpolicyagent.ideaplugin.OpaTestCase +import org.openpolicyagent.ideaplugin.TestProject + +abstract class OpaTestBase : BasePlatformTestCase(), OpaTestCase { + + open val dataPath: String = "" + + override fun getTestDataPath(): String = "${OpaTestCase.testResourcesPath}/${dataPath}" + + protected val fileName: String + get() = "$testName.rego" + + protected val testName: String + get() = camelOrWordsToSnake(getTestName(true)) + + companion object { + @JvmStatic + fun camelOrWordsToSnake(name: String): String { + if (' ' in name) return name.trim().replace(" ", "_") + + return name.split("(?=[A-Z])".toRegex()).joinToString("_", transform = String::toLowerCase) + } + } + + protected fun FileTree.create(): TestProject = + create(myFixture.project, myFixture.findFileInTempDir(".")) +} \ No newline at end of file diff --git a/src/test/kotlin/org/openpolicyagent/ideaplugin/ide/runconfig/test/OpaTestRunConfigurationBase.kt b/src/211/test/kotlin/org/openpolicyagent/ideaplugin/ide/runconfig/test/OpaTestRunConfigurationBase.kt similarity index 98% rename from src/test/kotlin/org/openpolicyagent/ideaplugin/ide/runconfig/test/OpaTestRunConfigurationBase.kt rename to src/211/test/kotlin/org/openpolicyagent/ideaplugin/ide/runconfig/test/OpaTestRunConfigurationBase.kt index 9c5b519..6819440 100644 --- a/src/test/kotlin/org/openpolicyagent/ideaplugin/ide/runconfig/test/OpaTestRunConfigurationBase.kt +++ b/src/211/test/kotlin/org/openpolicyagent/ideaplugin/ide/runconfig/test/OpaTestRunConfigurationBase.kt @@ -162,7 +162,7 @@ class ToStringPrinter : Printer { printable.printOn(this); } - override fun printHyperlink(text: String?, info: HyperlinkInfo?) {} + override fun printHyperlink(text: String, info: HyperlinkInfo?) {} override fun mark() {} diff --git a/src/212/main/kotlin/org/openpolicyagent/ideaplugin/ide/actions/utils.kt b/src/212/main/kotlin/org/openpolicyagent/ideaplugin/ide/actions/utils.kt new file mode 100644 index 0000000..25c5375 --- /dev/null +++ b/src/212/main/kotlin/org/openpolicyagent/ideaplugin/ide/actions/utils.kt @@ -0,0 +1,96 @@ +/* + * Use of this source code is governed by the MIT license that can be + * found in the LICENSE file. + */ + +package org.openpolicyagent.ideaplugin.ide.actions + + +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.actionSystem.CommonDataKeys +import com.intellij.openapi.editor.Document +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.fileEditor.FileEditorManager +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiManager +import com.intellij.psi.search.FilenameIndex +import com.intellij.psi.search.GlobalSearchScope +import org.openpolicyagent.ideaplugin.lang.RegoLanguage +import org.openpolicyagent.ideaplugin.lang.psi.RegoImport +import org.openpolicyagent.ideaplugin.lang.psi.RegoPackage +import org.openpolicyagent.ideaplugin.openapiext.virtualFile + +/** + * returns a nullable Pair containing the [Project] and the [Document] + * + * If the project is null or the file is not a Rego file then return null. + */ +fun getProjectAndDocument(e: AnActionEvent): Pair? { + val project = e.project ?: return null + val editor = e.getData(CommonDataKeys.EDITOR_EVEN_IF_INACTIVE) ?: getSelectedEditor(project) ?: return null + val document = editor.document + + return Pair(project, document) + +} + +fun getEditor(e: AnActionEvent): Editor? { + val project = e.project ?: return null + return e.getData(CommonDataKeys.EDITOR_EVEN_IF_INACTIVE) ?: getSelectedEditor(project) ?: return null +} + +fun getSelectedEditor(project: Project): Editor? = + FileEditorManager.getInstance(project).selectedTextEditor + +/** + * Returns package name in document as string + */ +fun getPackageAsString(document: Document, project: Project): String { + val file = document.virtualFile ?: return "" + val fileView = PsiManager.getInstance(project).findViewProvider(file) ?: return "" + val psiTree = fileView.getPsi(RegoLanguage) + val children = psiTree.children + for (child in children) { + if (child is RegoPackage) { + return child.ref.text + } + } + return "" +} + +/** + * Returns a list of the names of the imports in document as strings + */ +fun getImportsAsString(document: Document, project: Project): MutableList { + val file = document.virtualFile ?: return mutableListOf() + val fileView = PsiManager.getInstance(project).findViewProvider(file) ?: return mutableListOf() + val psiTree = fileView.getPsi(RegoLanguage) + val children = psiTree.children + var imports = mutableListOf() + for (child in children) { + if (child is RegoImport) { + var str = child.ref.text + val v = child.`var` + if (v != null) { + str += " as " + str += v.text + } + imports.add(str) + } + } + return imports +} + +/** + * Returns whether the project base directory has a file with name + * as direct child (rather than in subdirectory) + */ +fun fileDirectChildOfRoot(project: Project, name: String): Boolean { + val allfiles = FilenameIndex.getVirtualFilesByName(name, GlobalSearchScope.allScope(project)) + for (file in allfiles) { + if (file.parent.path == project.basePath) { + return true + } + } + return false +} diff --git a/src/212/main/kotlin/org/openpolicyagent/ideaplugin/ide/runconfig/test/go/time.kt b/src/212/main/kotlin/org/openpolicyagent/ideaplugin/ide/runconfig/test/go/time.kt new file mode 100644 index 0000000..59a9aff --- /dev/null +++ b/src/212/main/kotlin/org/openpolicyagent/ideaplugin/ide/runconfig/test/go/time.kt @@ -0,0 +1,219 @@ +/* + * Use of this source code is governed by the MIT license that can be + * found in the LICENSE file. + */ + +// IMPORTANT NOTE: this file is a portage of time/format.go in kotlin + +// Original Copyright: +// +// Copyright 2010 The Go Authors. All rights reserved. +// license that can be found in the docs/devel/golang/golang-license.md file. +package org.openpolicyagent.ideaplugin.ide.runconfig.test.go + +private const val Nanosecond: Long = 1 +private const val Microsecond: Long = 1000 * Nanosecond +private const val Millisecond: Long = 1000 * Microsecond +private const val Second: Long = 1000 * Millisecond +private const val Minute: Long = 60 * Second +private const val Hour: Long = 60 * Minute + + +private val unitMap = mapOf( + "ns" to Nanosecond, + "us" to Microsecond, + "µs" to Microsecond, // U+00B5 = micro symbol + "μs" to Microsecond, // U+03BC = Greek letter mu + "ms" to Millisecond, + "s" to Second, + "m" to Minute, + "h" to Hour +) + + +/** + * A Duration represents the elapsed time between two instants + * as an [Long] nanosecond count. The representation limits the + * largest representable duration to approximately 290 years. + */ +class Duration(private val value: Long) { + fun toMilliseconds(): Long { + return value / 1000000 // 1e6 + } +} + +/** + * Throw when the conversion of a string to a Duration failed because the input string was not valid + */ +class DurationFormatException(msg: String) : NumberFormatException(msg) + + +/** + * ParseDuration parses a duration string. + * A duration string is a possibly signed sequence of + * decimal numbers, each with optional fraction and a unit suffix, + * such as "300ms", "-1.5h" or "2h45m". + * Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". + * + * Throws [DurationFormatException] if the s is not a valid duration + */ +fun parseDuration(s1: String): Duration { + var s = s1 + // [-+]?([0-9]*(\.[0-9]*)?[a-z]+)+ + val orig = s + var d: Long = 0 + var neg = false + + // Consume [-+]? + if (s != "") { + val c = s[0] + if (c == '-' || c == '+') { + neg = c == '-' + s = s.substring(1) + } + } + // Special case: if all that is left is "0", this is zero. + if (s == "0") { + return Duration(0) + } + if (s == "") { + throw DurationFormatException("invalid duration ${orig}") + } + while (s != "") { + + var f: Long = 0 // integers before, after decimal point + var v: Long + var scale: Double = 1.0 // value = v + f/scale + + // The next character must be [0-9.] + if (!(s[0] == '.' || s[0] in '0'..'9')) { + throw DurationFormatException("invalid duration ${orig}") + } + // Consume [0-9]* + var pl = s.length + val (vt, st) = leadingInt(s) + v = vt + s = st + + val pre = pl != s.length // whether we consumed anything before a period + + // Consume (\.[0-9]*)? + var post = false + if (s != "" && s[0] == '.') { + s = s.substring(1) + pl = s.length + val (ft, scaleTemp, stt) = leadingFraction(s) + f = ft + scale = scaleTemp.toDouble() + s = stt + post = pl != s.length + } + if (!pre && !post) { + // no digits (e.g. ".s" or "-.s") + throw DurationFormatException("invalid duration $orig") + } + + // Consume unit. + + var i = 0 + while (i < s.length) { + val c = s[i] + if (c == '.' || c in '0'..'9') { + break + } + i++ + } + if (i == 0) { + throw DurationFormatException("missing unit in duration $orig") + } + val u = s.substring(0, i) + s = s.substring(i) + + + val unit = unitMap[u] ?: throw DurationFormatException("unknown unit $u in duration $orig") + if (v > Long.MAX_VALUE / unit) { + // overflow + throw DurationFormatException("invalid duration $orig") + } + v *= unit + if (f > 0) { + // float64 is needed to be nanosecond accurate for fractions of hours. + // v >= 0 && (f*unit/scale) <= 3.6e+12 (ns/h, h is the largest unit) + v += (f.toDouble() * (unit.toDouble() / scale)).toLong() + if (v < 0) { + // overflow + throw DurationFormatException("invalid duration $orig") + } + } + d += v + if (d < 0) { + // overflow + throw DurationFormatException("invalid duration $orig") + } + } + + if (neg) { + d = -d + } + return Duration(d) +} + +/** + * leadingInt consumes the leading [0-9]* from s. + */ +private fun leadingInt(s: String): Pair { + var i = 0 + var x: Long = 0 + while (i < s.length) { + val c = s[i] + if (c < '0' || c > '9') { + break + } + if (x > Long.MAX_VALUE / 10) { + // overflow + throw DurationFormatException("bad [0-9]*") + } + x = x * 10 + c.code.toLong() - '0'.code.toLong() + if (x < 0) { + // overflow + throw DurationFormatException("bad [0-9]*") + } + i++ + } + return Pair(x, s.substring(i)) +} + +/** + * leadingFraction consumes the leading [0-9]* from s. + * It is used only for fractions, so does not return an error on overflow, + * it just stops accumulating precision. + */ +private fun leadingFraction(s: String): Triple { + var i = 0 + var x: Long = 0 + var scale: Long = 1 + var overflow = false + while (i < s.length) { + val c = s[i] + if (c < '0' || c > '9') { + break + } + if (overflow) { + continue + } + if (x > Long.MAX_VALUE / 10) { + // It's possible for overflow to give a positive number, so take care. + overflow = true + continue + } + val y = x * 10 + c.code.toLong() - '0'.code.toLong() + if (y < 0) { + overflow = true + continue + } + x = y + scale *= 10 + i++ + } + return Triple(x, scale, s.substring(i)) +} diff --git a/src/test/kotlin/org/openpolicyagent/ideaplugin/OpaTestBase.kt b/src/212/test/kotlin/org/openpolicyagent/ideaplugin/OpaTestBase.kt similarity index 95% rename from src/test/kotlin/org/openpolicyagent/ideaplugin/OpaTestBase.kt rename to src/212/test/kotlin/org/openpolicyagent/ideaplugin/OpaTestBase.kt index bbd97c3..31ef991 100644 --- a/src/test/kotlin/org/openpolicyagent/ideaplugin/OpaTestBase.kt +++ b/src/212/test/kotlin/org/openpolicyagent/ideaplugin/OpaTestBase.kt @@ -24,7 +24,7 @@ abstract class OpaTestBase : BasePlatformTestCase(), OpaTestCase { fun camelOrWordsToSnake(name: String): String { if (' ' in name) return name.trim().replace(" ", "_") - return name.split("(?=[A-Z])".toRegex()).joinToString("_", transform = String::toLowerCase) + return name.split("(?=[A-Z])".toRegex()).joinToString("_", transform = String::lowercase) } } diff --git a/src/212/test/kotlin/org/openpolicyagent/ideaplugin/ide/runconfig/test/OpaTestRunConfigurationBase.kt b/src/212/test/kotlin/org/openpolicyagent/ideaplugin/ide/runconfig/test/OpaTestRunConfigurationBase.kt new file mode 100644 index 0000000..56480bb --- /dev/null +++ b/src/212/test/kotlin/org/openpolicyagent/ideaplugin/ide/runconfig/test/OpaTestRunConfigurationBase.kt @@ -0,0 +1,169 @@ +/* + * Use of this source code is governed by the MIT license that can be + * found in the LICENSE file. + */ + +package org.openpolicyagent.ideaplugin.ide.runconfig.test + +import com.intellij.execution.configuration.EnvironmentVariablesData +import com.intellij.execution.configurations.RunConfiguration +import com.intellij.execution.filters.HyperlinkInfo +import com.intellij.execution.testframework.Printable +import com.intellij.execution.testframework.Printer +import com.intellij.execution.testframework.sm.runner.SMTestProxy +import com.intellij.execution.testframework.sm.runner.ui.SMTRunnerConsoleView +import com.intellij.execution.ui.ConsoleViewContentType +import com.intellij.openapi.util.Disposer +import com.intellij.openapi.util.io.FileUtil +import com.intellij.util.ui.UIUtil +import org.assertj.core.api.Assertions.assertThat +import org.openpolicyagent.ideaplugin.OpaTestCase +import org.openpolicyagent.ideaplugin.ide.runconfig.OpaConfigurationFactory +import org.openpolicyagent.ideaplugin.ide.runconfig.RunConfigurationTestBase +import java.nio.file.Path +import java.nio.file.Paths + +abstract class OpaTestRunConfigurationBase : RunConfigurationTestBase() { + + fun createTestConfig( + bundleDir: Path? = null, + additionalArgs: String? = null, + env: EnvironmentVariablesData = EnvironmentVariablesData.DEFAULT + ): OpaTestRunConfiguration { + val runConfig = OpaConfigurationFactory(OpaTestRunConfigurationType()) + .createTemplateConfiguration(myFixture.project) as OpaTestRunConfiguration + + runConfig.bundleDir = bundleDir + runConfig.additionalArgs = additionalArgs + runConfig.env = env + return runConfig + } + + /** + * Run the test configuration and return the root of the testTree. + * + * see [checkTreeErrorMsg] and [getFormattedTestTree] for possible assertions + */ + protected fun executeAndGetTestRoot(configuration: RunConfiguration): SMTestProxy.SMRootTestProxy { + val result = execute(configuration) + val executionConsole = result.executionConsole as SMTRunnerConsoleView + val testsRootNode = executionConsole.resultsViewer.testsRootNode + with(result.processHandler) { + startNotify() + waitFor() + } + UIUtil.dispatchAllInvocationEvents() + Disposer.register(project, executionConsole) + return testsRootNode + } + + /** + * return the test tree as string for easy comparison with an expected output + * ( because it's a string, Intellij can generate a diff view if the test fails). + * + * Example: + * [root](-) + * .data.test.main.test_rule_2_should_be_ko(-) + * .data.test.main.test_should_raise_error(-) + * .data.test.main.test_rule1_should_be_ok(+) + * + * This method has been borrowed to IntelliJ rust project + */ + protected fun getFormattedTestTree(testTreeRoot: SMTestProxy.SMRootTestProxy) = + buildString { + if (testTreeRoot.wasTerminated()) { + append("Test terminated") + return@buildString + } + formatLevel(testTreeRoot) + } + + private fun StringBuilder.formatLevel(test: SMTestProxy, level: Int = 0) { + append(".".repeat(level)) + append(test.name) + when { + test.wasTerminated() -> append("[T]") + test.isPassed -> append("(+)") + test.isIgnored -> append("(~)") + else -> append("(-)") + } + + for (child in test.children) { + append('\n') + formatLevel(child, level + 1) + } + } + + /** + * check that the error message of each node matches the desired pattern + * the error message should correspond to the key "message" in [org.openpolicyagent.ideaplugin.ide.runconfig.test.OpaTestEventsConverter.fireFailedTest]) + * + * Desired patterns in file named "${node.name}.regex" are stored in the folder at "src/test/resources/${dataPath}/{testName}" + * + * eg. [org.openpolicyagent.ideaplugin.ide.runconfig.test.TestRunConfigurationExecutionOpaTest] + * + */ + protected fun checkTreeErrorMsg(root: SMTestProxy) { + val allNodes = mutableListOf(root) + allNodes.addAll(root.children) + + for (node in allNodes) { + val pattern = + FileUtil.loadFile( + Paths.get("${OpaTestCase.testResourcesPath}/${dataPath}/${testName}/${node.name}.regex").toFile() + ) + + val value = if (node == root) node.output else node.errorMessage ?: "" + + + assertThat(value) + .describedAs("test node '${node.name}' does not contain the expected error message") + .matches(Regex(pattern, RegexOption.MULTILINE).toPattern()) + } + + } + + /** + * name of the test currently being executed + */ + private val testName: String + get() = camelOrWordsToSnake(getTestName(true)) + + companion object { + @JvmStatic + fun camelOrWordsToSnake(name: String): String { + if (' ' in name) return name.trim().replace(" ", "_") + + return name.split("(?=[A-Z])".toRegex()).joinToString("_", transform = String::lowercase) + } + + private val SMTestProxy.output: String + get() { + val printer = ToStringPrinter() + printOn(printer) + return printer.output + } + } +} + +/** + * Fake printer that append text to a StringBuilder. It used to collect the output of the test root node. + */ +class ToStringPrinter : Printer { + private val buffer = StringBuilder() + + val output get() = buffer.toString() + + override fun print(text: String, contentType: ConsoleViewContentType) { + buffer.append(text) + } + + override fun onNewAvailable(printable: Printable) { + printable.printOn(this); + } + + override fun printHyperlink(text: String, info: HyperlinkInfo?) {} + + override fun mark() {} + +} \ No newline at end of file diff --git a/src/main/kotlin/org/openpolicyagent/ideaplugin/ide/linemarkers/OpaCommandRunLineMarker.kt b/src/main/kotlin/org/openpolicyagent/ideaplugin/ide/linemarkers/OpaCommandRunLineMarker.kt index 1133f04..42b4b10 100644 --- a/src/main/kotlin/org/openpolicyagent/ideaplugin/ide/linemarkers/OpaCommandRunLineMarker.kt +++ b/src/main/kotlin/org/openpolicyagent/ideaplugin/ide/linemarkers/OpaCommandRunLineMarker.kt @@ -61,12 +61,12 @@ class OpaCommandRunLineMarker : RunLineMarkerContributor() { if (element.elementType != RegoTypes.ASCII_LETTER) return null if (element.parent.parent.parent is RegoPackage) { - return Info(AllIcons.RunConfigurations.TestState.Run, { "eval or test package" }, ExecutorAction.getActions(1)) + return Info(AllIcons.RunConfigurations.TestState.Run, { "eval or test package" }, *ExecutorAction.getActions(1)) } if (element.parent.parent is RegoRuleHead) { val cmd = if(element.text.startsWith(REGO_TEST_RULE_PREFIX)) "test" else "eval" - return Info(AllIcons.RunConfigurations.TestState.Run, { "$cmd rule" }, ExecutorAction.getActions(1)) + return Info(AllIcons.RunConfigurations.TestState.Run, { "$cmd rule" }, *ExecutorAction.getActions(1)) } return null diff --git a/src/main/kotlin/org/openpolicyagent/ideaplugin/openapiext/utils.kt b/src/main/kotlin/org/openpolicyagent/ideaplugin/openapiext/utils.kt index f0399d5..e175749 100644 --- a/src/main/kotlin/org/openpolicyagent/ideaplugin/openapiext/utils.kt +++ b/src/main/kotlin/org/openpolicyagent/ideaplugin/openapiext/utils.kt @@ -130,7 +130,7 @@ val Document.isOPAPluginApplicable: Boolean val VirtualFile.document: Document? get() = FileDocumentManager.getInstance().getDocument(this) -inline fun getElements( +inline fun getElements( indexKey: StubIndexKey, key: Key, project: Project, scope: GlobalSearchScope? @@ -197,14 +197,14 @@ inline fun testAssert(action: () -> Boolean, lazyMessage: () -> Any) { fun runWithCheckCanceled(callable: () -> T): T = ApplicationUtil.runWithCheckCanceled(callable, ProgressManager.getInstance().progressIndicator) -fun Project.computeWithCancelableProgress(title: String, supplier: () -> T): T { +fun Project.computeWithCancelableProgress(title: String, supplier: () -> T): T { if (isUnitTestMode) { return supplier() } return ProgressManager.getInstance().runProcessWithProgressSynchronously(supplier, title, true, this) } -inline fun UserDataHolderEx.getOrPut(key: Key, defaultValue: () -> T): T = +inline fun UserDataHolderEx.getOrPut(key: Key, defaultValue: () -> T): T = getUserData(key) ?: putUserDataIfAbsent(key, defaultValue()) inline fun UserDataHolderEx.getOrPutSoft(key: Key>, defaultValue: () -> T): T = diff --git a/src/test/kotlin/org/openpolicyagent/ideaplugin/ide/highlight/AnnotatorTestBase.kt b/src/test/kotlin/org/openpolicyagent/ideaplugin/ide/highlight/AnnotatorTestBase.kt index 684346d..ba09e05 100644 --- a/src/test/kotlin/org/openpolicyagent/ideaplugin/ide/highlight/AnnotatorTestBase.kt +++ b/src/test/kotlin/org/openpolicyagent/ideaplugin/ide/highlight/AnnotatorTestBase.kt @@ -23,8 +23,7 @@ abstract class AnnotatorTestBase(private val annotatorClass: KClass