-
Notifications
You must be signed in to change notification settings - Fork 241
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
301 additions
and
6 deletions.
There are no files selected for viewing
4 changes: 4 additions & 0 deletions
4
.changes/next-release/feature-9c367c5b-9143-469d-a720-f720aca675d9.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
{ | ||
"type" : "feature", | ||
"description" : "Support NodeJs SAM debugging" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,4 +22,4 @@ task testJar (type: Jar) { | |
|
||
artifacts { | ||
testArtifacts testJar | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
223 changes: 223 additions & 0 deletions
223
...kits/jetbrains/services/lambda/nodejs/NodeJsLocalLambdaRunConfigurationIntegrationTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,223 @@ | ||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package software.aws.toolkits.jetbrains.services.lambda.nodejs | ||
|
||
import com.intellij.execution.executors.DefaultDebugExecutor | ||
import com.intellij.testFramework.runInEdtAndWait | ||
import com.intellij.xdebugger.XDebuggerUtil | ||
import org.assertj.core.api.Assertions.assertThat | ||
import org.junit.After | ||
import org.junit.Before | ||
import org.junit.Rule | ||
import org.junit.Test | ||
import org.junit.runner.RunWith | ||
import org.junit.runners.Parameterized | ||
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials | ||
import software.amazon.awssdk.services.lambda.model.Runtime | ||
import software.aws.toolkits.jetbrains.core.credentials.MockCredentialsManager | ||
import software.aws.toolkits.jetbrains.services.lambda.execution.local.createHandlerBasedRunConfiguration | ||
import software.aws.toolkits.jetbrains.services.lambda.execution.local.createTemplateRunConfiguration | ||
import software.aws.toolkits.jetbrains.services.lambda.sam.SamOptions | ||
import software.aws.toolkits.jetbrains.services.lambda.sam.checkBreakPointHit | ||
import software.aws.toolkits.jetbrains.services.lambda.sam.executeLambda | ||
import software.aws.toolkits.jetbrains.settings.SamSettings | ||
import softwere.aws.toolkits.jetbrains.utils.rules.NodeJsCodeInsightTestFixtureRule | ||
import softwere.aws.toolkits.jetbrains.utils.rules.addPackageJsonFile | ||
|
||
@RunWith(Parameterized::class) | ||
class NodeJsLocalLambdaRunConfigurationIntegrationTest(private val runtime: Runtime) { | ||
companion object { | ||
@JvmStatic | ||
@Parameterized.Parameters(name = "{0}") | ||
fun parameters(): Collection<Array<Runtime>> = listOf( | ||
arrayOf(Runtime.NODEJS8_10), | ||
arrayOf(Runtime.NODEJS10_X) | ||
) | ||
} | ||
|
||
@Rule | ||
@JvmField | ||
val projectRule = NodeJsCodeInsightTestFixtureRule() | ||
|
||
private val mockId = "MockCredsId" | ||
private val mockCreds = AwsBasicCredentials.create("Access", "ItsASecret") | ||
|
||
@Before | ||
fun setUp() { | ||
SamSettings.getInstance().savedExecutablePath = System.getenv()["SAM_CLI_EXEC"] | ||
|
||
val fixture = projectRule.fixture | ||
|
||
val psiFile = fixture.addFileToProject( | ||
"hello_world/app.js", | ||
""" | ||
exports.lambdaHandler = async (event, context) => { | ||
return 'Hello World' | ||
}; | ||
""".trimIndent() | ||
) | ||
|
||
runInEdtAndWait { | ||
fixture.openFileInEditor(psiFile.virtualFile) | ||
} | ||
|
||
MockCredentialsManager.getInstance().addCredentials(mockId, mockCreds) | ||
} | ||
|
||
@After | ||
fun tearDown() { | ||
MockCredentialsManager.getInstance().reset() | ||
} | ||
|
||
@Test | ||
fun samIsExecuted() { | ||
projectRule.fixture.addPackageJsonFile() | ||
|
||
val runConfiguration = createHandlerBasedRunConfiguration( | ||
project = projectRule.project, | ||
runtime = runtime, | ||
handler = "hello_world/app.lambdaHandler", | ||
input = "\"Hello World\"", | ||
credentialsProviderId = mockId | ||
) | ||
|
||
assertThat(runConfiguration).isNotNull | ||
|
||
val executeLambda = executeLambda(runConfiguration) | ||
|
||
assertThat(executeLambda.exitCode).isEqualTo(0) | ||
assertThat(executeLambda.stdout).contains("Hello World") | ||
} | ||
|
||
@Test | ||
fun samIsExecutedWithContainer() { | ||
projectRule.fixture.addPackageJsonFile() | ||
|
||
val samOptions = SamOptions().apply { | ||
this.buildInContainer = true | ||
} | ||
|
||
val runConfiguration = createHandlerBasedRunConfiguration( | ||
project = projectRule.project, | ||
runtime = runtime, | ||
handler = "hello_world/app.lambdaHandler", | ||
input = "\"Hello World\"", | ||
credentialsProviderId = mockId, | ||
samOptions = samOptions | ||
) | ||
|
||
assertThat(runConfiguration).isNotNull | ||
|
||
val executeLambda = executeLambda(runConfiguration) | ||
|
||
assertThat(executeLambda.exitCode).isEqualTo(0) | ||
assertThat(executeLambda.stdout).contains("Hello World") | ||
} | ||
|
||
@Test | ||
fun samIsExecutedWhenRunWithATemplateServerless() { | ||
projectRule.fixture.addPackageJsonFile(subPath = "hello_world") | ||
|
||
val templateFile = projectRule.fixture.addFileToProject( | ||
"template.yaml", """ | ||
Resources: | ||
SomeFunction: | ||
Type: AWS::Serverless::Function | ||
Properties: | ||
Handler: app.lambdaHandler | ||
CodeUri: hello_world | ||
Runtime: $runtime | ||
Timeout: 900 | ||
""".trimIndent() | ||
) | ||
|
||
val runConfiguration = createTemplateRunConfiguration( | ||
project = projectRule.project, | ||
templateFile = templateFile.containingFile.virtualFile.path, | ||
logicalId = "SomeFunction", | ||
input = "\"Hello World\"", | ||
credentialsProviderId = mockId | ||
) | ||
|
||
assertThat(runConfiguration).isNotNull | ||
|
||
val executeLambda = executeLambda(runConfiguration) | ||
|
||
assertThat(executeLambda.exitCode).isEqualTo(0) | ||
assertThat(executeLambda.stdout).contains("Hello World") | ||
} | ||
|
||
@Test | ||
fun samIsExecutedWithDebugger() { | ||
projectRule.fixture.addPackageJsonFile() | ||
|
||
val runConfiguration = createHandlerBasedRunConfiguration( | ||
project = projectRule.project, | ||
runtime = runtime, | ||
handler = "hello_world/app.lambdaHandler", | ||
input = "\"Hello World\"", | ||
credentialsProviderId = mockId | ||
) | ||
|
||
assertThat(runConfiguration).isNotNull | ||
|
||
addBreakpoint(2) | ||
|
||
val debuggerIsHit = checkBreakPointHit(projectRule.project) | ||
val executeLambda = executeLambda(runConfiguration, DefaultDebugExecutor.EXECUTOR_ID) | ||
|
||
assertThat(executeLambda.exitCode).isEqualTo(137) | ||
assertThat(executeLambda.stdout).contains("Hello World") | ||
|
||
assertThat(debuggerIsHit.get()).isTrue() | ||
} | ||
|
||
@Test | ||
fun samIsExecutedWithDebugger_sameFileNames() { | ||
projectRule.fixture.addPackageJsonFile() | ||
|
||
val psiFile = projectRule.fixture.addFileToProject( | ||
"hello_world/subfolder/app.js", | ||
""" | ||
exports.lambdaHandler = async (event, context) => { | ||
return 'Hello World' | ||
}; | ||
""".trimIndent() | ||
) | ||
|
||
runInEdtAndWait { | ||
projectRule.fixture.openFileInEditor(psiFile.virtualFile) | ||
} | ||
|
||
val runConfiguration = createHandlerBasedRunConfiguration( | ||
project = projectRule.project, | ||
runtime = runtime, | ||
handler = "hello_world/subfolder/app.lambdaHandler", | ||
input = "\"Hello World\"", | ||
credentialsProviderId = mockId | ||
) | ||
|
||
assertThat(runConfiguration).isNotNull | ||
|
||
addBreakpoint(2) | ||
|
||
val debuggerIsHit = checkBreakPointHit(projectRule.project) | ||
val executeLambda = executeLambda(runConfiguration, DefaultDebugExecutor.EXECUTOR_ID) | ||
|
||
assertThat(executeLambda.exitCode).isEqualTo(137) | ||
assertThat(executeLambda.stdout).contains("Hello World") | ||
|
||
assertThat(debuggerIsHit.get()).isTrue() | ||
} | ||
|
||
private fun addBreakpoint(lineNumber: Int) { | ||
runInEdtAndWait { | ||
XDebuggerUtil.getInstance().toggleLineBreakpoint( | ||
projectRule.project, | ||
projectRule.fixture.file.virtualFile, | ||
lineNumber | ||
) | ||
} | ||
} | ||
} |
70 changes: 68 additions & 2 deletions
70
...imate/src/software/aws/toolkits/jetbrains/services/lambda/nodejs/NodeJsSamDebugSupport.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,83 @@ | ||
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package software.aws.toolkits.jetbrains.services.lambda.nodejs | ||
|
||
import com.google.common.collect.BiMap | ||
import com.google.common.collect.HashBiMap | ||
import com.intellij.execution.process.ProcessAdapter | ||
import com.intellij.execution.process.ProcessEvent | ||
import com.intellij.execution.runners.ExecutionEnvironment | ||
import com.intellij.javascript.debugger.LocalFileSystemFileFinder | ||
import com.intellij.javascript.debugger.RemoteDebuggingFileFinder | ||
import com.intellij.openapi.util.io.FileUtil | ||
import com.intellij.openapi.vfs.VirtualFile | ||
import com.intellij.util.Urls | ||
import com.intellij.xdebugger.XDebugProcess | ||
import com.intellij.xdebugger.XDebugProcessStarter | ||
import com.intellij.xdebugger.XDebugSession | ||
import com.jetbrains.debugger.wip.WipLocalVmConnection | ||
import com.jetbrains.nodeJs.NodeChromeDebugProcess | ||
import org.jetbrains.io.LocalFileFinder | ||
import software.aws.toolkits.jetbrains.services.lambda.execution.PathMapping | ||
import software.aws.toolkits.jetbrains.services.lambda.execution.local.SamDebugSupport | ||
import software.aws.toolkits.jetbrains.services.lambda.execution.local.SamRunningState | ||
import java.net.InetSocketAddress | ||
|
||
// TODO provide implementation of Node.js debugger support | ||
class NodeJsSamDebugSupport : SamDebugSupport { | ||
override fun createDebugProcess( | ||
environment: ExecutionEnvironment, | ||
state: SamRunningState, | ||
debugPort: Int | ||
): XDebugProcessStarter? = null | ||
): XDebugProcessStarter? = object : XDebugProcessStarter() { | ||
override fun start(session: XDebugSession): XDebugProcess { | ||
val mappings = createBiMapMappings(state.builtLambda.mappings) | ||
val fileFinder = RemoteDebuggingFileFinder(mappings, LocalFileSystemFileFinder(false)) | ||
val connection = WipLocalVmConnection() | ||
val executionResult = state.execute(environment.executor, environment.runner) | ||
|
||
val process = NodeChromeDebugProcess(session, fileFinder, connection, executionResult) | ||
|
||
val processHandler = executionResult.processHandler | ||
val socketAddress = InetSocketAddress("localhost", debugPort) | ||
|
||
if (processHandler == null || processHandler.isStartNotified) { | ||
connection.open(socketAddress) | ||
} else { | ||
processHandler.addProcessListener(object : ProcessAdapter() { | ||
override fun startNotified(event: ProcessEvent) { | ||
connection.open(socketAddress) | ||
} | ||
}) | ||
} | ||
return process | ||
} | ||
} | ||
|
||
/** | ||
* Convert [PathMapping] to NodeJs debugger path mapping format. | ||
* | ||
* Docker uses the same project structure for dependencies in the folder node_modules. We map the source code and | ||
* the dependencies in node_modules folder separately as the node_modules might not exist in the local project. | ||
*/ | ||
private fun createBiMapMappings(pathMapping: List<PathMapping>): BiMap<String, VirtualFile> { | ||
val mappings = HashBiMap.create<String, VirtualFile>(pathMapping.size) | ||
|
||
listOf(".", NODE_MODULES).forEach { subPath -> | ||
pathMapping.forEach { | ||
val remotePath = FileUtil.toCanonicalPath("$TASK_PATH/${it.remoteRoot}/$subPath") | ||
val remoteUrl = Urls.newUri("file", remotePath).toString() | ||
LocalFileFinder.findFile("${it.localRoot}/$subPath")?.let { localFile -> | ||
mappings.putIfAbsent(remoteUrl, localFile) | ||
} | ||
} | ||
} | ||
|
||
return mappings | ||
} | ||
|
||
private companion object { | ||
const val TASK_PATH = "/var/task" | ||
const val NODE_MODULES = "node_modules" | ||
} | ||
} |