Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 21 additions & 7 deletions src/main/kotlin/com/codingtestkit/service/CodeRunner.kt
Original file line number Diff line number Diff line change
Expand Up @@ -619,7 +619,7 @@ else console.log(_result);
if (compile.exitCode != 0) {
return RunResult(output = "", error = I18n.t("컴파일 에러", "Compile error") + ":\n${compile.error}", exitCode = compile.exitCode)
}
return executeProcess(listOf(javaPath, "-cp", dir.absolutePath, "Main"), dir, input, timeout)
return executeProcess(javaCommand("-cp", dir.absolutePath, "Main"), dir, input, timeout)
}

val className = detectJavaClassName(code)
Expand All @@ -641,7 +641,7 @@ else console.log(_result);
if (compile.exitCode != 0) {
return RunResult(output = "", error = I18n.t("컴파일 에러", "Compile error") + ":\n${compile.error}", exitCode = compile.exitCode)
}
return executeProcess(listOf(javaPath, "-cp", dir.absolutePath, "Main"), dir, input, timeout)
return executeProcess(javaCommand("-cp", dir.absolutePath, "Main"), dir, input, timeout)
}

val sourceFile = File(dir, "$className.java")
Expand All @@ -652,13 +652,27 @@ else console.log(_result);
return RunResult(output = "", error = I18n.t("컴파일 에러", "Compile error") + ":\n${compile.error}", exitCode = compile.exitCode)
}

return executeProcess(listOf(javaPath, "-cp", dir.absolutePath, className), dir, input, timeout)
return executeProcess(javaCommand("-cp", dir.absolutePath, className), dir, input, timeout)
}

private fun javacCommand(vararg sourceFiles: File): List<String> {
return listOf(javacPath, "-encoding", "UTF-8") + sourceFiles.map { it.absolutePath }
}

/**
* java 실행 커맨드. 자식 JVM의 표준 입출력 인코딩을 UTF-8로 강제한다.
* Windows 기본 코드페이지(MS949 등)로 인해 한글 결과가 깨지는 것을 방지 (이슈 #4).
* stdout/stderr.encoding은 JDK 18+에서 동작하고, 하위 버전에서는 무시되어 무해하다.
*/
private fun javaCommand(vararg args: String): List<String> {
return listOf(
javaPath,
"-Dfile.encoding=UTF-8",
"-Dstdout.encoding=UTF-8",
"-Dstderr.encoding=UTF-8"
) + args
}

private fun extractJavaClass(code: String, className: String): String {
val pattern = Regex("(class\\s+$className\\s*\\{)", RegexOption.DOT_MATCHES_ALL)
val match = pattern.find(code) ?: return code
Expand Down Expand Up @@ -741,7 +755,7 @@ else console.log(_result);
return RunResult(output = "", error = I18n.t("컴파일 에러", "Compile error") + ":\n${compile.error}", exitCode = compile.exitCode)
}

return executeProcess(listOf(javaPath, "-jar", jarFile.absolutePath), dir, input, timeout)
return executeProcess(javaCommand("-jar", jarFile.absolutePath), dir, input, timeout)
}

private fun runJavaScript(code: String, input: String, dir: File, timeout: Long): RunResult {
Expand All @@ -768,7 +782,7 @@ else console.log(_result);
.start()

if (input.isNotBlank()) {
process.outputStream.bufferedWriter().use { it.write(input) }
process.outputStream.bufferedWriter(Charsets.UTF_8).use { it.write(input) }
}

// 메모리 폴링 스레드 시작
Expand Down Expand Up @@ -815,8 +829,8 @@ else console.log(_result);
return RunResult(output = "", error = I18n.t("시간 초과 (${timeout}초)", "Time Limit Exceeded (${timeout}s)"), exitCode = -1, timedOut = true, executionTimeMs = elapsedMs, peakMemoryKB = peakMemory.get())
}

val output = process.inputStream.bufferedReader().readText().trimEnd()
val error = process.errorStream.bufferedReader().readText().trimEnd()
val output = process.inputStream.bufferedReader(Charsets.UTF_8).readText().trimEnd()
val error = process.errorStream.bufferedReader(Charsets.UTF_8).readText().trimEnd()

return RunResult(output = output, error = error, exitCode = process.exitValue(), executionTimeMs = elapsedMs, peakMemoryKB = peakMemory.get())
}
Expand Down
22 changes: 22 additions & 0 deletions src/test/kotlin/com/codingtestkit/service/CodeRunnerTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -404,4 +404,26 @@ class CodeRunnerTest {
dir.deleteRecursively()
}
}

/**
* Regression test for issue #4: the java run command must force UTF-8 standard I/O encoding.
*
* The runtime garbling only reproduces on a real Windows host (native.encoding = MS949), where
* `-Dstdout.encoding` cannot be overridden from the command line on macOS/Linux. So instead of
* an unreproducible end-to-end test, we assert the command `javaCommand` builds always carries
* the UTF-8 encoding flags — this fails OS-independently if the flags regress.
*/
@Test
fun `javaCommand forces UTF-8 standard IO encoding`() {
val method = CodeRunner::class.java.getDeclaredMethod("javaCommand", Array<String>::class.java)
method.isAccessible = true
@Suppress("UNCHECKED_CAST")
val cmd = method.invoke(CodeRunner, arrayOf("-cp", "X", "Main")) as List<String>

assertTrue(cmd.contains("-Dfile.encoding=UTF-8"), "missing file.encoding flag: $cmd")
assertTrue(cmd.contains("-Dstdout.encoding=UTF-8"), "missing stdout.encoding flag: $cmd")
assertTrue(cmd.contains("-Dstderr.encoding=UTF-8"), "missing stderr.encoding flag: $cmd")
// The caller's arguments must be preserved at the end of the command.
assertEquals(listOf("-cp", "X", "Main"), cmd.takeLast(3))
}
}
Loading