diff --git a/src/Runtime/XSharp.VFP.Tests/DirectoryTests.prg b/src/Runtime/XSharp.VFP.Tests/DirectoryTests.prg
new file mode 100644
index 0000000000..c3580ae9f4
--- /dev/null
+++ b/src/Runtime/XSharp.VFP.Tests/DirectoryTests.prg
@@ -0,0 +1,137 @@
+//
+// Copyright (c) XSharp B.V. All Rights Reserved.
+// Licensed under the Apache License, Version 2.0.
+// See License.txt in the project root for license information.
+//
+USING System
+USING System.IO
+USING XUnit
+
+BEGIN NAMESPACE XSharp.VFP.Tests
+
+ CLASS DirectoryTests
+ STATIC CONSTRUCTOR
+ XSharp.RuntimeState.Dialect := XSharpDialect.FoxPro
+ END CONSTRUCTOR
+
+ STATIC PRIVATE METHOD NewTempDir() AS STRING
+ LOCAL cDir AS STRING
+ cDir := System.IO.Path.Combine(System.IO.Path.GetTempPath(), "VfpDirectoryTest_" + Guid.NewGuid():ToString("N"))
+ System.IO.Directory.CreateDirectory(cDir)
+ RETURN cDir
+ END METHOD
+
+ STATIC PRIVATE METHOD Hide(cDir AS STRING) AS VOID
+ LOCAL oInfo AS System.IO.DirectoryInfo
+ oInfo := System.IO.DirectoryInfo{cDir}
+ oInfo:Attributes := (System.IO.FileAttributes) _OR((DWORD) System.IO.FileAttributes.Directory, ;
+ (DWORD) System.IO.FileAttributes.Hidden)
+ END METHOD
+
+ [Fact, Trait("Category", "File")];
+ METHOD DirectoryFindsAnExistingDirectory() AS VOID
+ LOCAL cDir AS STRING
+ cDir := NewTempDir()
+ TRY
+ Assert.True(Directory(cDir))
+ FINALLY
+ System.IO.Directory.Delete(cDir)
+ END TRY
+ END METHOD
+
+ [Fact, Trait("Category", "File")];
+ METHOD DirectoryAcceptsATrailingSeparator() AS VOID
+ LOCAL cDir AS STRING
+ cDir := NewTempDir()
+ TRY
+ Assert.True(Directory(cDir + System.IO.Path.DirectorySeparatorChar:ToString()))
+ FINALLY
+ System.IO.Directory.Delete(cDir)
+ END TRY
+ END METHOD
+
+ [Fact, Trait("Category", "File")];
+ METHOD DirectoryReturnsFalseForAMissingDirectory() AS VOID
+ Assert.False(Directory(System.IO.Path.Combine(System.IO.Path.GetTempPath(), "VfpDirectoryTest_" + Guid.NewGuid():ToString("N"))))
+ END METHOD
+
+ // a file is not a directory, VFP reports .F. for it
+ [Fact, Trait("Category", "File")];
+ METHOD DirectoryReturnsFalseForAFile() AS VOID
+ LOCAL cDir AS STRING
+ LOCAL cFile AS STRING
+ cDir := NewTempDir()
+ cFile := System.IO.Path.Combine(cDir, "afile.txt")
+ System.IO.File.WriteAllText(cFile, "x")
+ TRY
+ Assert.False(Directory(cFile))
+ FINALLY
+ System.IO.File.Delete(cFile)
+ System.IO.Directory.Delete(cDir)
+ END TRY
+ END METHOD
+
+ [Fact, Trait("Category", "File")];
+ METHOD DirectoryReturnsFalseForAnEmptyName() AS VOID
+ Assert.False(Directory(""))
+ END METHOD
+
+ [Fact, Trait("Category", "File")];
+ METHOD DirectoryHidesAHiddenDirectoryByDefault() AS VOID
+ LOCAL cDir AS STRING
+ cDir := NewTempDir()
+ Hide(cDir)
+ TRY
+ Assert.False(Directory(cDir))
+ FINALLY
+ System.IO.Directory.Delete(cDir)
+ END TRY
+ END METHOD
+
+ [Fact, Trait("Category", "File")];
+ METHOD DirectoryFindsAHiddenDirectoryWithFlagOne() AS VOID
+ LOCAL cDir AS STRING
+ cDir := NewTempDir()
+ Hide(cDir)
+ TRY
+ Assert.True(Directory(cDir, 1))
+ FINALLY
+ System.IO.Directory.Delete(cDir)
+ END TRY
+ END METHOD
+
+ // only nFlags 1 ignores the attributes, 2 behaves like 0
+ [Fact, Trait("Category", "File")];
+ METHOD DirectoryTreatsFlagsOtherThanOneAsZero() AS VOID
+ LOCAL cDir AS STRING
+ cDir := NewTempDir()
+ Hide(cDir)
+ TRY
+ Assert.False(Directory(cDir, 2))
+ FINALLY
+ System.IO.Directory.Delete(cDir)
+ END TRY
+ END METHOD
+
+ // Windows marks a drive root Hidden and System, VFP still reports it
+ [Fact, Trait("Category", "File")];
+ METHOD DirectoryFindsADriveRoot() AS VOID
+ Assert.True(Directory(System.IO.Path.GetPathRoot(Environment.SystemDirectory)))
+ END METHOD
+
+ [Fact, Trait("Category", "File")];
+ METHOD DirectoryResolvesARelativeName() AS VOID
+ LOCAL cName AS STRING
+ LOCAL cDir AS STRING
+ cName := "VfpDirectoryTest_" + Guid.NewGuid():ToString("N")
+ cDir := System.IO.Path.Combine(Environment.CurrentDirectory, cName)
+ System.IO.Directory.CreateDirectory(cDir)
+ TRY
+ Assert.True(Directory(cName))
+ FINALLY
+ System.IO.Directory.Delete(cDir)
+ END TRY
+ END METHOD
+
+ END CLASS
+END NAMESPACE
diff --git a/src/Runtime/XSharp.VFP/RuntimeCoreWrappers.prg b/src/Runtime/XSharp.VFP/RuntimeCoreWrappers.prg
index 1240700d3c..b0ad52b09f 100644
--- a/src/Runtime/XSharp.VFP/RuntimeCoreWrappers.prg
+++ b/src/Runtime/XSharp.VFP/RuntimeCoreWrappers.prg
@@ -23,7 +23,34 @@ FUNCTION File(cFileSpec AS STRING) AS LOGIC
///
[FoxProFunction("DIRECTORY", FoxFunctionCategory.FileAndIO, FoxEngine.RuntimeCore, FoxFunctionStatus.Full, FoxCriticality.High)];
FUNCTION Directory(cDirectoryName AS STRING, nFlags := 0 AS INT) AS LOGIC
- RETURN XSharp.RT.Functions.ALen(XSharp.RT.Functions.Directory(cDirectoryName)) > 0
+ LOCAL lFound := FALSE AS LOGIC
+ LOCAL cFullPath AS STRING
+ LOCAL oAttributes AS System.IO.FileAttributes
+
+ IF String.IsNullOrWhiteSpace(cDirectoryName)
+ RETURN FALSE
+ ENDIF
+
+ TRY
+ // SET DEFAULT syncs the process directory in the FoxPro dialect, so a
+ // relative name resolves against the VFP default directory
+ cFullPath := System.IO.Path.GetFullPath(cDirectoryName)
+ IF System.IO.Directory.Exists(cFullPath)
+ // only nFlags 1 ignores the attributes, and Windows marks a drive
+ // root Hidden and System while VFP still reports it
+ IF nFlags == 1 .OR. String.Compare(cFullPath, System.IO.Path.GetPathRoot(cFullPath), TRUE) == 0
+ lFound := TRUE
+ ELSE
+ oAttributes := System.IO.File.GetAttributes(cFullPath)
+ lFound := ! (oAttributes:HasFlag(System.IO.FileAttributes.Hidden) .OR. ;
+ oAttributes:HasFlag(System.IO.FileAttributes.System))
+ ENDIF
+ ENDIF
+ CATCH
+ lFound := FALSE
+ END TRY
+
+ RETURN lFound
///
[FoxProFunction("GETENV", FoxFunctionCategory.EnvironmentAndSystem, FoxEngine.RuntimeCore, FoxFunctionStatus.Full, FoxCriticality.Medium)];