1+ //===----------------------------------------------------------------------===//
2+ //
3+ // This source file is part of the Swift.org open source project
4+ //
5+ // Copyright (c) 2025 Apple Inc. and the Swift.org project authors
6+ // Licensed under Apache License v2.0
7+ //
8+ // See LICENSE.txt for license information
9+ // See CONTRIBUTORS.txt for the list of Swift.org project authors
10+ //
11+ // SPDX-License-Identifier: Apache-2.0
12+ //
13+ //===----------------------------------------------------------------------===//
14+
15+ @_spi ( Testing) import SwiftJava
16+ import SwiftJavaToolLib
17+ import JavaUtilJar
18+ import SwiftJavaShared
19+ import SwiftJavaConfigurationShared
20+ import _Subprocess
21+ import XCTest // NOTE: Workaround for https://github.com/swiftlang/swift-java/issues/43
22+
23+ fileprivate func createTemporaryDirectory( in directory: URL ) throws -> URL {
24+ let uuid = UUID ( ) . uuidString
25+ let resolverDirectoryURL = directory. appendingPathComponent ( " swift-java-testing- \( uuid) " )
26+
27+ try FileManager . default. createDirectory ( at: resolverDirectoryURL, withIntermediateDirectories: true , attributes: nil )
28+
29+ return resolverDirectoryURL
30+ }
31+
32+ /// Returns the directory that should be added to the classpath of the JVM to analyze the sources.
33+ func compileJava( _ sourceText: String ) async throws -> URL {
34+ let sourceFile = try TempFile . create ( suffix: " java " , sourceText)
35+
36+ let classesDirectory = try createTemporaryDirectory ( in: FileManager . default. temporaryDirectory)
37+
38+ let javacProcess = try await _Subprocess. run (
39+ . path( " /usr/bin/javac " ) ,
40+ arguments: [
41+ " -d " , classesDirectory. path, // output directory for .class files
42+ sourceFile. path
43+ ] ,
44+ output: . string( limit: Int . max, encoding: UTF8 . self) ,
45+ error: . string( limit: Int . max, encoding: UTF8 . self)
46+ )
47+
48+ // Check if compilation was successful
49+ guard javacProcess. terminationStatus. isSuccess else {
50+ let outString = javacProcess. standardOutput ?? " "
51+ let errString = javacProcess. standardError ?? " "
52+ fatalError ( " javac ' \( sourceFile) ' failed ( \( javacProcess. terminationStatus) ); \n " +
53+ " OUT: \( outString) \n " +
54+ " ERROR: \( errString) " )
55+ }
56+
57+ print ( " Compiled java sources to: \( classesDirectory) " )
58+ return classesDirectory
59+ }
60+
61+ func withJavaTranslator(
62+ javaClassNames: [ String ] ,
63+ classpath: [ URL ] ,
64+ body: ( JavaTranslator ) throws -> ( ) ,
65+ function: String = #function,
66+ file: StaticString = #filePath,
67+ line: UInt = #line
68+ ) throws {
69+ let jvm = try JavaVirtualMachine . shared (
70+ classpath: classpath. map ( \. path) ,
71+ replace: false
72+ )
73+
74+ var config = Configuration ( )
75+ config. minimumInputAccessLevelMode = . package
76+
77+ let environment = try jvm. environment ( )
78+ let translator = JavaTranslator (
79+ config: config,
80+ swiftModuleName: " SwiftModule " ,
81+ environment: environment,
82+ translateAsClass: true )
83+
84+ try body ( translator)
85+ }
86+
87+ /// Translate a Java class and assert that the translated output contains
88+ /// each of the expected "chunks" of text.
89+ func assertWrapJavaOutput(
90+ javaClassNames: [ String ] ,
91+ classpath: [ URL ] ,
92+ expectedChunks: [ String ] ,
93+ function: String = #function,
94+ file: StaticString = #filePath,
95+ line: UInt = #line
96+ ) throws {
97+ let jvm = try JavaVirtualMachine . shared (
98+ classpath: classpath. map ( \. path) ,
99+ replace: false
100+ )
101+
102+ var config = Configuration ( )
103+ config. minimumInputAccessLevelMode = . package
104+
105+ let environment = try jvm. environment ( )
106+ let translator = JavaTranslator (
107+ config: config,
108+ swiftModuleName: " SwiftModule " ,
109+ environment: environment,
110+ translateAsClass: true )
111+
112+ let classLoader = try ! JavaClass < JavaClassLoader > ( environment: environment)
113+ . getSystemClassLoader ( ) !
114+
115+
116+ // FIXME: deduplicate this
117+ translator. startNewFile ( )
118+
119+ var swiftCompleteOutputText = " "
120+
121+ var javaClasses : [ JavaClass < JavaObject > ] = [ ]
122+ for javaClassName in javaClassNames {
123+ guard let javaClass = try classLoader. loadClass ( javaClassName) else {
124+ fatalError ( " Could not load Java class ' \( javaClassName) ' in test \( function) @ \( file) : \( line) ! " )
125+ }
126+ javaClasses. append ( javaClass)
127+
128+ // FIXME: deduplicate this with SwiftJava.WrapJavaCommand.runCommand !!!
129+ // TODO: especially because nested classes
130+ // WrapJavaCommand().<TODO>
131+
132+ let swiftUnqualifiedName = javaClassName. javaClassNameToCanonicalName
133+ . defaultSwiftNameForJavaClass
134+ translator. translatedClasses [ javaClassName] =
135+ . init( module: nil , name: swiftUnqualifiedName)
136+
137+ try translator. validateClassConfiguration ( )
138+
139+ let swiftClassDecls = try translator. translateClass ( javaClass)
140+ let importDecls = translator. getImportDecls ( )
141+
142+ let swiftFileText =
143+ """
144+ // ---------------------------------------------------------------------------
145+ // Auto-generated by Java-to-Swift wrapper generator.
146+ \( importDecls. map { $0. description } . joined ( ) )
147+ \( swiftClassDecls. map { $0. description } . joined ( separator: " \n " ) )
148+ \n
149+ """
150+ swiftCompleteOutputText += swiftFileText
151+ }
152+
153+ for expectedChunk in expectedChunks {
154+ if swiftCompleteOutputText. contains ( expectedChunk) {
155+ continue
156+ }
157+
158+ XCTFail ( " Expected chunk: \n " +
159+ " \( expectedChunk. yellow) " +
160+ " \n " +
161+ " not found in: \n " +
162+ " \( swiftCompleteOutputText) " ,
163+ file: file, line: line)
164+ }
165+
166+ print ( swiftCompleteOutputText)
167+ }
0 commit comments