Conversation
There was a problem hiding this comment.
Pull request overview
Implements the Visual FoxPro-compatible FCREATE() and FOPEN() runtime wrappers in XSharp.VFP by delegating to XSharp.Core file APIs and adds unit tests to validate expected handle/error behavior.
Changes:
- Implemented
FCreate()to callXSharp.Core.Functions.FCreate()and return the handle asINT64. - Implemented
FOpen()with VFPnAttribute→FO_*mode translation (including unbuffered modes) and SET PATH resolution viaFile()/FPathName(). - Added unit tests covering create/open success paths, missing-file error return, and SET PATH lookup.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
src/Runtime/XSharp.VFP/RuntimeCoreWrappers.prg |
Adds concrete implementations for FCreate() and FOpen() and marks both functions as Full. |
src/Runtime/XSharp.VFP.Tests/MiscTests.prg |
Adds file I/O unit tests for FCreate()/FOpen() and SET PATH behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| CASE 10; nMode := FO_READ | FO_SHARED | FO_UNBUFFERED | ||
| CASE 11; nMode := FO_WRITE | FO_EXCLUSIVE | FO_UNBUFFERED | ||
| CASE 12; nMode := FO_READWRITE | FO_EXCLUSIVE | FO_UNBUFFERED | ||
| CASE 0 |
There was a problem hiding this comment.
In the SWITCH on nAttribute, CASE 0 does not assign nMode, so the behavior relies on nMode’s implicit default value. This makes the default open mode ambiguous and easy to break if initialization rules change. Assign the intended default mode explicitly for CASE 0 (or initialize nMode when declared) so FOpen(cFileName, 0) is self-documenting and consistent with the other cases.
| CASE 0 | |
| CASE 0; nMode := FO_READ | FO_SHARED |
| LOCAL nBadHandle AS INT64 | ||
| nBadHandle := FOpen("non_existent_file.txt", 0) |
There was a problem hiding this comment.
This test’s expected-failure case uses a hardcoded relative filename ("non_existent_file.txt"). If that file happens to exist in the current working directory (or is found via a prior SET PATH), the test will become flaky and fail unexpectedly. Use a unique temp path (e.g., GUID-based under Path.GetTempPath()) and assert it does not exist before calling FOpen.
| LOCAL nBadHandle AS INT64 | |
| nBadHandle := FOpen("non_existent_file.txt", 0) | |
| LOCAL cBadFile AS STRING | |
| LOCAL nBadHandle AS INT64 | |
| cBadFile := Path.Combine(Path.GetTempPath(), "vfp_test_" + Guid.NewGuid():ToString("N") + ".txt") | |
| Assert.False(FILE(cBadFile)) | |
| nBadHandle := FOpen(cBadFile, 0) |
|
|
||
| cFile := Path.Combine(Path.GetTempPath(), "vfp_test_" + Guid.NewGuid():ToString("N") + ".txt") | ||
|
|
||
| && 1. Test FCREATE() | ||
| nHandle := FCreate(cFile, 0) | ||
| Assert.True(nHandle > 0, "FCreate should return a valid handle") | ||
|
|
||
| nWritten := FPuts(nHandle, "XSharp VFP File Test") | ||
| Assert.True(nWritten > 0, "Should have written data in the file") | ||
| FClose(nHandle) | ||
|
|
||
| && 2. Test FOPEN() | ||
| nHandle := FOpen(cFile, 0) | ||
| Assert.True(nHandle > 0, "FOpen should open existing file") | ||
| FClose(nHandle) | ||
|
|
||
| && 3. Test expected failure (non existent file) | ||
| LOCAL nBadHandle AS INT64 | ||
| nBadHandle := FOpen("non_existent_file.txt", 0) | ||
| Assert.Equal(-1L, nBadHandle) | ||
|
|
||
| IF FILE(cFile) | ||
| File.Delete(cFile) | ||
| ENDIF |
There was a problem hiding this comment.
FCreateAndFOpenTest() deletes the temp file only at the end of the method. If an assertion fails earlier, the temp file and/or an open handle may be left behind, which can pollute subsequent runs. Wrap the test body in TRY/FINALLY and ensure FClose() (when needed) and deletion of cFile happen in FINALLY.
| cFile := Path.Combine(Path.GetTempPath(), "vfp_test_" + Guid.NewGuid():ToString("N") + ".txt") | |
| && 1. Test FCREATE() | |
| nHandle := FCreate(cFile, 0) | |
| Assert.True(nHandle > 0, "FCreate should return a valid handle") | |
| nWritten := FPuts(nHandle, "XSharp VFP File Test") | |
| Assert.True(nWritten > 0, "Should have written data in the file") | |
| FClose(nHandle) | |
| && 2. Test FOPEN() | |
| nHandle := FOpen(cFile, 0) | |
| Assert.True(nHandle > 0, "FOpen should open existing file") | |
| FClose(nHandle) | |
| && 3. Test expected failure (non existent file) | |
| LOCAL nBadHandle AS INT64 | |
| nBadHandle := FOpen("non_existent_file.txt", 0) | |
| Assert.Equal(-1L, nBadHandle) | |
| IF FILE(cFile) | |
| File.Delete(cFile) | |
| ENDIF | |
| LOCAL nBadHandle AS INT64 | |
| cFile := Path.Combine(Path.GetTempPath(), "vfp_test_" + Guid.NewGuid():ToString("N") + ".txt") | |
| nHandle := 0 | |
| TRY | |
| && 1. Test FCREATE() | |
| nHandle := FCreate(cFile, 0) | |
| Assert.True(nHandle > 0, "FCreate should return a valid handle") | |
| nWritten := FPuts(nHandle, "XSharp VFP File Test") | |
| Assert.True(nWritten > 0, "Should have written data in the file") | |
| IF nHandle > 0 | |
| FClose(nHandle) | |
| nHandle := 0 | |
| ENDIF | |
| && 2. Test FOPEN() | |
| nHandle := FOpen(cFile, 0) | |
| Assert.True(nHandle > 0, "FOpen should open existing file") | |
| IF nHandle > 0 | |
| FClose(nHandle) | |
| nHandle := 0 | |
| ENDIF | |
| && 3. Test expected failure (non existent file) | |
| nBadHandle := FOpen("non_existent_file.txt", 0) | |
| Assert.Equal(-1L, nBadHandle) | |
| FINALLY | |
| IF nHandle > 0 | |
| FClose(nHandle) | |
| ENDIF | |
| IF FILE(cFile) | |
| File.Delete(cFile) | |
| ENDIF | |
| END TRY |
nAttributefile modes to X# nativeFO_*andFC_*constants.FOPEN(modes 10, 11, 12 mapped toFO_UNBUFFERED).StubtoFull.