Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow to run unit-tests on Windows #2287

Merged
merged 19 commits into from Aug 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
f0f2696
Implement java.io needed methods + add bindings
WojciechMazur May 5, 2021
e933f88
Add specific Windows and Unix Socket implementation. Add bindings.
WojciechMazur May 5, 2021
3eeaac6
Make java.util.Date compilable on Windows
WojciechMazur May 5, 2021
3dca0a6
Add java.io.File methods Windows implementations needed for testsExt
WojciechMazur May 5, 2021
9f7f24c
Generate debug symbols to allow gathering stacktraces on Windows
WojciechMazur May 5, 2021
0a54840
Fix value of Invalid_handle_value, allow to run testExt tests
WojciechMazur May 6, 2021
60afca8
Add CI step to run testsExt unit tests
WojciechMazur May 6, 2021
52d5f86
Fix 2.13 warning and errors
WojciechMazur May 6, 2021
a4e4dbf
Move windows specific time methods to windowslib
WojciechMazur May 6, 2021
245ee97
Limit GC limits, due to failures in CI (not enough memory to allocate)
WojciechMazur May 6, 2021
611014d
After rebase fixes
WojciechMazur Jun 1, 2021
54c849c
Formatting fixes
WojciechMazur Jun 1, 2021
f0d4eae
Add missing FileOutputStream constructor
WojciechMazur Jun 1, 2021
f5fcfa5
Add missing EOF lines
WojciechMazur Jun 1, 2021
69ff48f
Add comment about difference in StackTraceElement parsing
WojciechMazur Jun 1, 2021
abc206c
Move HelperMethods.scala to javalib, make package private
WojciechMazur Jun 1, 2021
d70e618
Rearrange order of LLVM flags to make them more readable
WojciechMazur Jun 1, 2021
7f5c6c2
Applied after review fixes and fixed typos. Adjusted access scope for…
WojciechMazur Jun 1, 2021
e75335e
Rerun scalafmt with new config
WojciechMazur Jul 10, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 7 additions & 4 deletions .github/workflows/run-tests-windows.yml
Expand Up @@ -81,8 +81,8 @@ jobs:
$cSources | ForEach-Object {clang -std=gnu11 -c "$_"}
$cppSources | ForEach-Object {clang++ -std=c++14 -c "$_"}

run-sandbox:
name: Run sandbox project
run-tests:
name: Run tests
runs-on: ${{matrix.os}}
strategy:
fail-fast: false
Expand Down Expand Up @@ -121,8 +121,11 @@ jobs:
run: clang --version

- name: Run sandbox
# Set heap limits manually, there seams to be bug in detection of available memory
# which results in undefined behavior at runtime
run: |
sbt ++${{matrix.scala}};sandbox/run
shell: cmd

- name: Run testsExt tests
run: |
sbt ++${{matrix.scala}};testsExt/test
shell: cmd
165 changes: 124 additions & 41 deletions javalib/src/main/scala/java/io/File.scala
Expand Up @@ -2,26 +2,34 @@ package java.io

import java.nio.file.{FileSystems, Path}
import java.net.URI

import java.nio.charset.StandardCharsets
import scala.annotation.tailrec
import scalanative.annotation.stub
import scalanative.posix.{fcntl, limits, unistd, utime}
import scalanative.annotation.{alwaysinline, stub}
import scalanative.posix.{limits, unistd, utime}
import scalanative.posix.sys.stat
import scalanative.unsigned._
import scalanative.unsafe._
import scalanative.libc._, stdlib._, stdio._, string._
import scalanative.libc._
import stdlib._
import stdio._
import string._
import scalanative.nio.fs.FileHelpers
import scalanative.runtime.{DeleteOnExit, Platform}
import unistd._
import scala.scalanative.meta.LinktimeInfo.isWindows
import scala.scalanative.windows
import windows.FileApi._
import windows.FileApiExt._
import scala.scalanative.windows.WinBaseApi._
import scala.scalanative.windows.winnt.AccessRights._
import java.util.WindowsHelperMethods._

class File(_path: String) extends Serializable with Comparable[File] {
import File._

if (_path == null) throw new NullPointerException()
private val path: String = fixSlashes(_path)
private[io] val properPath: String = File.properPath(path)
private[io] val properPathBytes: Array[Byte] =
File.properPath(path).getBytes("UTF-8")

def this(parent: String, child: String) =
this(
Expand All @@ -31,10 +39,8 @@ class File(_path: String) extends Serializable with Comparable[File] {
def this(parent: File, child: String) =
this(Option(parent).map(_.path).orNull, child)

def this(uri: URI) = {
this(uri.getPath())
checkURI(uri)
}
def this(uri: URI) =
this(File.checkURI(uri).getPath())

def compareTo(file: File): Int = {
if (caseSensitive) getPath().compareTo(file.getPath())
Expand Down Expand Up @@ -86,8 +92,8 @@ class File(_path: String) extends Serializable with Comparable[File] {
}
}

def exists(): Boolean =
Zone { implicit z => access(toCString(path), unistd.F_OK) == 0 }
@inline
def exists(): Boolean = FileHelpers.exists(path)

def toPath(): Path =
FileSystems.getDefault().getPath(this.getPath(), Array.empty)
Expand All @@ -104,8 +110,14 @@ class File(_path: String) extends Serializable with Comparable[File] {
private def deleteDirImpl(): Boolean =
Zone { implicit z => remove(toCString(path)) == 0 }

private def deleteFileImpl(): Boolean =
Zone { implicit z => unlink(toCString(path)) == 0 }
private def deleteFileImpl(): Boolean = Zone { implicit z =>
if (isWindows) {
setReadOnlyWindows(enabled = false)
DeleteFileW(toCWideStringUTF16LE(path))
} else {
unlink(toCString(path)) == 0
}
}

override def equals(that: Any): Boolean =
that match {
Expand Down Expand Up @@ -140,7 +152,8 @@ class File(_path: String) extends Serializable with Comparable[File] {
resolvedName
}

/** Finds the canonical path for `path`. */
/** Finds the canonical path for `path`.
*/
private def simplifyNonExistingPath(path: String): String =
path
.split(separatorChar)
Expand Down Expand Up @@ -186,13 +199,26 @@ class File(_path: String) extends Serializable with Comparable[File] {
File.isAbsolute(path)

def isDirectory(): Boolean =
Zone { implicit z => stat.S_ISDIR(accessMode()) != 0 }
Zone { implicit z =>
if (isWindows)
fileAttributeIsSet(FILE_ATTRIBUTE_DIRECTORY)
else
stat.S_ISDIR(accessMode()) != 0
}

def isFile(): Boolean =
Zone { implicit z => stat.S_ISREG(accessMode()) != 0 }
Zone { implicit z =>
if (isWindows)
fileAttributeIsSet(FILE_ATTRIBUTE_NORMAL) || !isDirectory()
else
stat.S_ISREG(accessMode()) != 0
}

def isHidden(): Boolean =
getName().startsWith(".")
def isHidden(): Boolean = {
if (isWindows)
fileAttributeIsSet(FILE_ATTRIBUTE_HIDDEN)
else getName().startsWith(".")
}

def lastModified(): Long =
Zone { implicit z =>
Expand All @@ -213,6 +239,12 @@ class File(_path: String) extends Serializable with Comparable[File] {
}
}

@alwaysinline
private def fileAttributeIsSet(attribute: windows.DWord): Boolean = Zone {
implicit z =>
(GetFileAttributesW(toCWideStringUTF16LE(path)) & attribute) == attribute
}

def setLastModified(time: Long): Boolean =
if (time < 0) {
throw new IllegalArgumentException("Negative time")
Expand All @@ -231,22 +263,45 @@ class File(_path: String) extends Serializable with Comparable[File] {

def setReadOnly(): Boolean =
Zone { implicit z =>
import stat._
val mask =
S_ISUID | S_ISGID | S_ISVTX | S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH
val newMode = accessMode() & mask
chmod(toCString(path), newMode) == 0
if (isWindows) setReadOnlyWindows(enabled = true)
else {
import stat._
val mask =
S_ISUID | S_ISGID | S_ISVTX | S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH
val newMode = accessMode() & mask
chmod(toCString(path), newMode) == 0
}
}

def length(): Long =
Zone { implicit z =>
private def setReadOnlyWindows(enabled: Boolean)(implicit z: Zone) = {
val filename = toCWideStringUTF16LE(path)
val currentAttributes = GetFileAttributesW(filename)

def newAttributes =
if (enabled) currentAttributes | FILE_ATTRIBUTE_READONLY
else currentAttributes & ~FILE_ATTRIBUTE_READONLY

def setNewAttributes() = SetFileAttributesW(filename, newAttributes)

currentAttributes != INVALID_FILE_ATTRIBUTES && setNewAttributes()
}

def length(): Long = Zone { implicit z =>
if (isWindows) {
withFileOpen(path, access = FILE_READ_ATTRIBUTES) { handle =>
val size = stackalloc[windows.LargeInteger]
if (GetFileSizeEx(handle, size)) (!size).toLong
else 0L
}
} else {
val buf = alloc[stat.stat]
if (stat.stat(toCString(path), buf) == 0) {
buf._6
} else {
0L
}
}
}

def list(): Array[String] =
list(FilenameFilter.allPassFilter)
Expand Down Expand Up @@ -316,7 +371,7 @@ class File(_path: String) extends Serializable with Comparable[File] {

// Ported from Apache Harmony
def toURI(): URI = {
val path = getAbsolutePath()
val path = getAbsolutePath().replace('\\', '/')
if (!path.startsWith("/")) {
// start with sep.
new URI(
Expand Down Expand Up @@ -344,9 +399,16 @@ object File {

private def getUserDir(): String =
Zone { implicit z =>
var buff: CString = alloc[CChar](4096.toUInt)
var res: CString = getcwd(buff, 4095.toUInt)
fromCString(res)
if (isWindows) {
val buffSize = GetCurrentDirectoryW(0.toUInt, null)
val buff = alloc[windows.WChar](buffSize + 1.toUInt)
GetCurrentDirectoryW(buffSize, buff)
fromCWideString(buff, StandardCharsets.UTF_16LE)
} else {
val buff: CString = alloc[CChar](4096.toUInt)
getcwd(buff, 4095.toUInt)
fromCString(buff)
}
}

/** The purpose of this method is to take a path and fix the slashes up. This
Expand Down Expand Up @@ -379,10 +441,12 @@ object File {
}
} else {
// check for leading slashes before a drive
if (currentChar == ':' && uncIndex > 0 &&
(newLength == 2 ||
(newLength == 3 && newPath(1) == separatorChar)) &&
newPath(0) == separatorChar) {
if (currentChar == ':'
&& uncIndex > 0
&& (newLength == 2 || (newLength == 3 && newPath(
1
) == separatorChar))
&& newPath(0) == separatorChar) {
newPath(0) = newPath(newLength - 1)
newLength = 1
// allow trailing slash after drive letter
Expand All @@ -396,9 +460,9 @@ object File {
i += 1
}

if (foundSlash &&
(newLength > (uncIndex + 1) ||
(newLength == 2 && newPath(0) != separatorChar))) {
if (foundSlash && (newLength > (uncIndex + 1) || (newLength == 2 && newPath(
0
) != separatorChar))) {
newLength -= 1
}

Expand All @@ -411,6 +475,20 @@ object File {
// Ported from Apache Harmony
private def properPath(path: String): String = {
if (isAbsolute(path)) path
else if (isWindows) Zone { implicit z =>
val pathCString = toCWideStringUTF16LE(path)
val bufSize = GetFullPathNameW(pathCString, 0.toUInt, null, null)
val buf = stackalloc[windows.WChar](bufSize)
if (GetFullPathNameW(
pathCString,
bufferLength = bufSize,
buffer = buf,
filePart = null
) == 0.toUInt) {
throw new IOException("Failed to resolve correct path")
}
fromCWideString(buf, StandardCharsets.UTF_16LE)
}
else {
val userdir =
Option(getUserDir())
Expand All @@ -429,8 +507,9 @@ object File {
def isAbsolute(path: String): Boolean =
if (separatorChar == '\\') { // Windows. Must start with `\\` or `X:(\|/)`
(path.length > 1 && path.startsWith(separator + separator)) ||
(path.length > 2 && path(0).isLetter && path(1) == ':' &&
(path(2) == '/' || path(2) == '\\'))
(path.length > 2 && path(0).isLetter && path(1) == ':' && (path(
2
) == '/' || path(2) == '\\'))
} else {
path.length > 0 && path.startsWith(separator)
}
Expand Down Expand Up @@ -480,7 +559,10 @@ object File {
@tailrec private def resolve(path: CString, start: UInt = 0.toUInt)(implicit
z: Zone
): CString = {
val part: CString = alloc[Byte](limits.PATH_MAX.toUInt)
val partSize =
if (isWindows) windows.FileApiExt.MAX_PATH
else limits.PATH_MAX.toUInt
val part: CString = alloc[Byte](partSize)
val `1U` = 1.toUInt
// Find the next separator
var i = start
Expand Down Expand Up @@ -554,7 +636,7 @@ object File {
)

// Ported from Apache Harmony
private def checkURI(uri: URI): Unit = {
private def checkURI(uri: URI): URI = {
def throwExc(msg: String): Unit =
throw new IllegalArgumentException(s"$msg: $uri")
def compMsg(comp: String): String =
Expand All @@ -575,6 +657,7 @@ object File {
} else if (uri.getRawFragment() != null) {
throwExc(compMsg("fragment"))
}
uri
// else URI is ok
}
}
3 changes: 2 additions & 1 deletion javalib/src/main/scala/java/io/FileOutputStream.scala
Expand Up @@ -14,8 +14,9 @@ import scala.scalanative.windows.FileApiExt._
import scala.scalanative.windows.HandleApiExt._
import scala.scalanative.windows.winnt.AccessRights._

class FileOutputStream(fd: FileDescriptor, file: Option[File] = None)
class FileOutputStream(fd: FileDescriptor, file: Option[File])
extends OutputStream {
def this(fd: FileDescriptor) = this(fd, None)
def this(file: File, append: Boolean) =
this(FileOutputStream.fileDescriptor(file, append), Some(file))
def this(file: File) = this(file, false)
Expand Down
6 changes: 5 additions & 1 deletion javalib/src/main/scala/java/lang/StackTraceElement.scala
Expand Up @@ -2,6 +2,7 @@ package java.lang

import scalanative.unsafe.{CString, fromCString}
import scalanative.libc.string.strlen
import scalanative.runtime.Platform.isWindows

final class StackTraceElement(
val getClassName: String,
Expand Down Expand Up @@ -52,7 +53,10 @@ private[lang] object StackTraceElement {
var methodName = ""

def readSymbol(): Boolean = {
if (read() != '_') {
// On Windows symbol names are different then on Unix platforms.
// Due to differences in implementation between WinDbg and libUnwind used
// on each platform, symbols on Windows do not contain '_' prefix.
if (!isWindows() && read() != '_') {
WojciechMazur marked this conversation as resolved.
Show resolved Hide resolved
false
} else if (read() != 'S') {
false
Expand Down