fix(validator,field-test): unblock Fanuc/Mitsubishi upload (ISS-018, ISS-019)
ISS-018: FanucProgram.Standardize() prepends a leading '\n' to the
program body, so the O#### header is not on lines[0]. The validator
first-line check read lines[0] and always failed "First line must be
O####", rejecting every valid Fanuc/Mitsubishi upload. Scan the first
non-blank line instead. Upload (FOCAS tape) format unchanged.
ISS-019: field-test .bat files passed -pathprogramma unquoted; on
install paths with spaces ("visual studio prove") cmd split the arg and
ArgParser captured only the first token -> truncated path. Quote the
arg in all 7 scripts.
Add regression test Validate_LeadingBlankLineBeforeONumber for both
Fanuc and Mitsubishi.
Surfaced by field testing against a local docker FTP server.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3fd3047584
commit
85d0b579aa
10 changed files with 66 additions and 19 deletions
|
|
@ -182,6 +182,32 @@ Enhancements and known issues discovered during execution and codebase analysis.
|
|||
|
||||
---
|
||||
|
||||
### ~~ISS-018: `Standardize()` `\n`-prefix breaks `NcProgramValidator` first-line check~~ ✅ CLOSED
|
||||
|
||||
- **Discovered:** Field test docker FTP (2026-06-02)
|
||||
- **Closed:** 2026-06-02
|
||||
- **Type:** Bug (CRITICAL)
|
||||
- **Description:** `FanucProgram.Standardize()` prepends `"\n"` to `Program` (`FanucProgram.cs:147-153`). `NcProgramValidator` first-line check reads `lines[0]` and expects `^O\d{4}` (`NcProgramValidator.cs:40-46`). After standardize, `lines[0] == ""` → check always fails `"First line must be O#### program number"`. **Every Fanuc/Mitsubishi upload rejected**, including valid `O0001(TEST HENESIS)`. Confirmed via field test: all 5 T03 samples + T01 + T07 INVIO returned `-204`.
|
||||
- **Files:** `Cnc/NcProgramValidator.cs`, `FanucProgram.cs`
|
||||
- **Fix applied:** validator first-line check now scans first **non-blank** line instead of `lines[0]`, tolerating the leading `\n` that `Standardize()` injects. Upload format unchanged (FOCAS tape format preserved).
|
||||
- **Impact:** Critical — no valid program uploadable for Fanuc/Mitsubishi
|
||||
- **Effort:** Quick
|
||||
|
||||
---
|
||||
|
||||
### ~~ISS-019: `-pathprogramma` truncated at first space (unquoted `.bat` args)~~ ✅ CLOSED
|
||||
|
||||
- **Discovered:** Field test docker FTP (2026-06-02)
|
||||
- **Closed:** 2026-06-02
|
||||
- **Type:** Bug (CRITICAL)
|
||||
- **Description:** Field-test `.bat` files pass `-pathprogramma=%~dp0samples\...` and `%RESULTS%\...` **unquoted**. When the install path contains spaces (`...\visual studio prove\...`), cmd splits the argument into multiple argv tokens. `ArgParser.cs:73` `Find` matches only the first token → `Substring(15)` yields a truncated path (e.g. `C:\Users\trent\Desktop\davide\visual`). Effect: SCARICA writes the download to the wrong file (still exit 0, masking failure); INVIA later reads that stray file → garbage. Explains the observed T02→T01 interaction.
|
||||
- **Files:** `field-test/t01_invia_valido.bat`, `t02_scarica.bat`, `t03_validatore.bat`, `t04_errori_connessione.bat`, `t05_offset_non_supportati.bat`, `t07_roundtrip.bat`
|
||||
- **Fix applied:** all `-pathprogramma=...` args quoted (`-pathprogramma="..."`) across the 6 `.bat` files. `ArgParser` itself is correct when argv is passed properly (Windows delivers a quoted spaced path as a single token).
|
||||
- **Impact:** Critical — all file I/O hit wrong path on space-containing install dirs
|
||||
- **Effort:** Quick
|
||||
|
||||
---
|
||||
|
||||
## Closed Issues
|
||||
|
||||
| ID | Summary | Phase | Commit |
|
||||
|
|
@ -192,8 +218,10 @@ Enhancements and known issues discovered during execution and codebase analysis.
|
|||
| ISS-007 | Integration tests use hardcoded IPs | 14 | `32f68c5` |
|
||||
| ISS-003 | Silent exception handlers swallow failures | 15 | (Phase 15 commit) |
|
||||
| ISS-004 | No structured logging framework | 15 | (Phase 15 commit) |
|
||||
| ISS-018 | Standardize() \n-prefix breaks validator first-line check | field-test | (pending) |
|
||||
| ISS-019 | -pathprogramma truncated at first space (unquoted .bat) | field-test | (pending) |
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 2026-05-25*
|
||||
*Sources: STATE.md deferred issues + CONCERNS.md codebase analysis*
|
||||
*Last updated: 2026-06-02*
|
||||
*Sources: STATE.md deferred issues + CONCERNS.md codebase analysis + field-test docker FTP*
|
||||
|
|
|
|||
|
|
@ -36,12 +36,19 @@ namespace NcProgramManager.Cnc
|
|||
string normalized = content.Replace("\r", "");
|
||||
string[] lines = normalized.Split('\n');
|
||||
|
||||
// First-line check (Fanuc/Mitsubishi)
|
||||
if ((_manufacturer == CncManufacturer.Fanuc || _manufacturer == CncManufacturer.Mitsubishi)
|
||||
&& lines.Length > 0)
|
||||
// First-line check (Fanuc/Mitsubishi).
|
||||
// FanucProgram.Standardize() prepends a leading '\n' to the program body,
|
||||
// so the O#### header is not necessarily on lines[0]. Scan the first
|
||||
// non-blank line instead of assuming index 0.
|
||||
if (_manufacturer == CncManufacturer.Fanuc || _manufacturer == CncManufacturer.Mitsubishi)
|
||||
{
|
||||
string first = lines[0].Trim();
|
||||
if (!FanucFirstLinePattern.IsMatch(first))
|
||||
string first = null;
|
||||
for (int li = 0; li < lines.Length; li++)
|
||||
{
|
||||
string t = lines[li].Trim();
|
||||
if (t.Length > 0) { first = t; break; }
|
||||
}
|
||||
if (first == null || !FanucFirstLinePattern.IsMatch(first))
|
||||
errors.Add(new ValidationError(1, "First line must be O#### program number", ValidationSeverity.Error));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -129,6 +129,18 @@ namespace NcProgramManager.Tests.Unit
|
|||
Assert.That(HasErrorContaining(r, "First line"), Is.True);
|
||||
}
|
||||
|
||||
[TestCase(CncManufacturer.Fanuc)]
|
||||
[TestCase(CncManufacturer.Mitsubishi)]
|
||||
public void Validate_LeadingBlankLineBeforeONumber_NoFirstLineError(CncManufacturer mfg)
|
||||
{
|
||||
// ISS-018: FanucProgram.Standardize() prepends a leading '\n', so the
|
||||
// O#### header lands on line[1]. First-line check must scan past blanks.
|
||||
var v = new NcProgramValidator(mfg);
|
||||
var r = v.Validate("O1234", "\nO1234\nG00 X10 Y20\nM30");
|
||||
Assert.That(HasErrorContaining(r, "First line"), Is.False);
|
||||
Assert.That(r.Success, Is.True);
|
||||
}
|
||||
|
||||
[TestCase(CncManufacturer.Fanuc, "O1234", "O1234\nG00 X10;")]
|
||||
[TestCase(CncManufacturer.Mitsubishi, "O1234", "O1234\nG00 X10;")]
|
||||
[TestCase(CncManufacturer.Siemens, "PROG.MPF", "G00 X10;")]
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ if not exist "%RESULTS%" mkdir "%RESULTS%"
|
|||
set LOG=%RESULTS%\t01_invia_valido.log
|
||||
|
||||
echo === T01 INVIO VALIDO === > "%LOG%"
|
||||
"%EXE%" -manufacturer=mitsubishi -azione=Invia -ip=%IP% -porta=%PORT% -username=%USER% -password=%PASS% -path=%PROGNUM% -pathprogramma=%~dp0samples\O0001_valid.nc >> "%LOG%" 2>&1
|
||||
"%EXE%" -manufacturer=mitsubishi -azione=Invia -ip=%IP% -porta=%PORT% -username=%USER% -password=%PASS% -path=%PROGNUM% -pathprogramma="%~dp0samples\O0001_valid.nc" >> "%LOG%" 2>&1
|
||||
echo EXITCODE=%ERRORLEVEL% >> "%LOG%"
|
||||
echo EXITCODE=%ERRORLEVEL%
|
||||
echo Log: %LOG%
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ set LOG=%RESULTS%\t02_scarica.log
|
|||
set OUT=%RESULTS%\downloaded.txt
|
||||
|
||||
echo === T02 SCARICO === > "%LOG%"
|
||||
"%EXE%" -manufacturer=mitsubishi -azione=Scarica -ip=%IP% -porta=%PORT% -username=%USER% -password=%PASS% -path=%PROGNUM% -pathprogramma=%OUT% >> "%LOG%" 2>&1
|
||||
"%EXE%" -manufacturer=mitsubishi -azione=Scarica -ip=%IP% -porta=%PORT% -username=%USER% -password=%PASS% -path=%PROGNUM% -pathprogramma="%OUT%" >> "%LOG%" 2>&1
|
||||
echo EXITCODE=%ERRORLEVEL% >> "%LOG%"
|
||||
echo EXITCODE=%ERRORLEVEL%
|
||||
echo File scaricato: %OUT%
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ echo === T03 VALIDATORE === > "%LOG%"
|
|||
for %%F in (O0001_valid O0002_lowercase O0003_brackets O0004_longblock no_header) do (
|
||||
echo. >> "%LOG%"
|
||||
echo ----- INVIO %%F ----- >> "%LOG%"
|
||||
"%EXE%" -manufacturer=mitsubishi -azione=Invia -ip=%IP% -porta=%PORT% -username=%USER% -password=%PASS% -path=%PROGNUM% -pathprogramma=%~dp0samples\%%F.nc >> "%LOG%" 2>&1
|
||||
"%EXE%" -manufacturer=mitsubishi -azione=Invia -ip=%IP% -porta=%PORT% -username=%USER% -password=%PASS% -path=%PROGNUM% -pathprogramma="%~dp0samples\%%F.nc" >> "%LOG%" 2>&1
|
||||
echo EXITCODE %%F = !ERRORLEVEL! >> "%LOG%"
|
||||
)
|
||||
echo Log: %LOG%
|
||||
|
|
|
|||
|
|
@ -18,17 +18,17 @@ echo === T04 ERRORI CONNESSIONE === > "%LOG%"
|
|||
|
||||
echo. >> "%LOG%"
|
||||
echo ----- a) IP inesistente 10.255.255.1 ----- >> "%LOG%"
|
||||
"%EXE%" -manufacturer=mitsubishi -azione=Scarica -ip=10.255.255.1 -porta=%PORT% -username=%USER% -password=%PASS% -path=%PROGNUM% -pathprogramma=%RESULTS%\dl_a.txt >> "%LOG%" 2>&1
|
||||
"%EXE%" -manufacturer=mitsubishi -azione=Scarica -ip=10.255.255.1 -porta=%PORT% -username=%USER% -password=%PASS% -path=%PROGNUM% -pathprogramma="%RESULTS%\dl_a.txt" >> "%LOG%" 2>&1
|
||||
echo EXITCODE_a=%ERRORLEVEL% >> "%LOG%"
|
||||
|
||||
echo. >> "%LOG%"
|
||||
echo ----- b) porta errata 9999 ----- >> "%LOG%"
|
||||
"%EXE%" -manufacturer=mitsubishi -azione=Scarica -ip=%IP% -porta=9999 -username=%USER% -password=%PASS% -path=%PROGNUM% -pathprogramma=%RESULTS%\dl_b.txt >> "%LOG%" 2>&1
|
||||
"%EXE%" -manufacturer=mitsubishi -azione=Scarica -ip=%IP% -porta=9999 -username=%USER% -password=%PASS% -path=%PROGNUM% -pathprogramma="%RESULTS%\dl_b.txt" >> "%LOG%" 2>&1
|
||||
echo EXITCODE_b=%ERRORLEVEL% >> "%LOG%"
|
||||
|
||||
echo. >> "%LOG%"
|
||||
echo ----- c) credenziali errate ----- >> "%LOG%"
|
||||
"%EXE%" -manufacturer=mitsubishi -azione=Scarica -ip=%IP% -porta=%PORT% -username=NOUSER -password=NOPASS -path=%PROGNUM% -pathprogramma=%RESULTS%\dl_c.txt >> "%LOG%" 2>&1
|
||||
"%EXE%" -manufacturer=mitsubishi -azione=Scarica -ip=%IP% -porta=%PORT% -username=NOUSER -password=NOPASS -path=%PROGNUM% -pathprogramma="%RESULTS%\dl_c.txt" >> "%LOG%" 2>&1
|
||||
echo EXITCODE_c=%ERRORLEVEL% >> "%LOG%"
|
||||
|
||||
echo Log: %LOG%
|
||||
|
|
|
|||
|
|
@ -14,12 +14,12 @@ echo === T05 OFFSET NON SUPPORTATI === > "%LOG%"
|
|||
|
||||
echo. >> "%LOG%"
|
||||
echo ----- Scarica con -tooloffset ----- >> "%LOG%"
|
||||
"%EXE%" -manufacturer=mitsubishi -azione=Scarica -ip=%IP% -porta=%PORT% -username=%USER% -password=%PASS% -path=%PROGNUM% -pathprogramma=%RESULTS%\dl_tool.txt -tooloffset >> "%LOG%" 2>&1
|
||||
"%EXE%" -manufacturer=mitsubishi -azione=Scarica -ip=%IP% -porta=%PORT% -username=%USER% -password=%PASS% -path=%PROGNUM% -pathprogramma="%RESULTS%\dl_tool.txt" -tooloffset >> "%LOG%" 2>&1
|
||||
echo EXITCODE_tool=%ERRORLEVEL% >> "%LOG%"
|
||||
|
||||
echo. >> "%LOG%"
|
||||
echo ----- Scarica con -workzero ----- >> "%LOG%"
|
||||
"%EXE%" -manufacturer=mitsubishi -azione=Scarica -ip=%IP% -porta=%PORT% -username=%USER% -password=%PASS% -path=%PROGNUM% -pathprogramma=%RESULTS%\dl_wz.txt -workzero >> "%LOG%" 2>&1
|
||||
"%EXE%" -manufacturer=mitsubishi -azione=Scarica -ip=%IP% -porta=%PORT% -username=%USER% -password=%PASS% -path=%PROGNUM% -pathprogramma="%RESULTS%\dl_wz.txt" -workzero >> "%LOG%" 2>&1
|
||||
echo EXITCODE_wz=%ERRORLEVEL% >> "%LOG%"
|
||||
|
||||
echo Log: %LOG%
|
||||
|
|
|
|||
|
|
@ -16,12 +16,12 @@ echo === T06 PARAMETRI MANCANTI === > "%LOG%"
|
|||
|
||||
echo. >> "%LOG%"
|
||||
echo ----- a) manca -azione ----- >> "%LOG%"
|
||||
"%EXE%" -manufacturer=mitsubishi -ip=%IP% -porta=%PORT% -pathprogramma=%RESULTS%\x.txt >> "%LOG%" 2>&1
|
||||
"%EXE%" -manufacturer=mitsubishi -ip=%IP% -porta=%PORT% -pathprogramma="%RESULTS%\x.txt" >> "%LOG%" 2>&1
|
||||
echo EXITCODE_a=%ERRORLEVEL% >> "%LOG%"
|
||||
|
||||
echo. >> "%LOG%"
|
||||
echo ----- b) manca -ip ----- >> "%LOG%"
|
||||
"%EXE%" -manufacturer=mitsubishi -azione=Scarica -porta=%PORT% -pathprogramma=%RESULTS%\x.txt >> "%LOG%" 2>&1
|
||||
"%EXE%" -manufacturer=mitsubishi -azione=Scarica -porta=%PORT% -pathprogramma="%RESULTS%\x.txt" >> "%LOG%" 2>&1
|
||||
echo EXITCODE_b=%ERRORLEVEL% >> "%LOG%"
|
||||
|
||||
echo. >> "%LOG%"
|
||||
|
|
|
|||
|
|
@ -17,12 +17,12 @@ echo === T07 ROUNDTRIP === > "%LOG%"
|
|||
|
||||
echo. >> "%LOG%"
|
||||
echo ----- 1) INVIO O0001_valid ----- >> "%LOG%"
|
||||
"%EXE%" -manufacturer=mitsubishi -azione=Invia -ip=%IP% -porta=%PORT% -username=%USER% -password=%PASS% -path=%PROGNUM% -pathprogramma=%~dp0samples\O0001_valid.nc >> "%LOG%" 2>&1
|
||||
"%EXE%" -manufacturer=mitsubishi -azione=Invia -ip=%IP% -porta=%PORT% -username=%USER% -password=%PASS% -path=%PROGNUM% -pathprogramma="%~dp0samples\O0001_valid.nc" >> "%LOG%" 2>&1
|
||||
echo EXITCODE_invio=%ERRORLEVEL% >> "%LOG%"
|
||||
|
||||
echo. >> "%LOG%"
|
||||
echo ----- 2) SCARICO %PROGNUM% ----- >> "%LOG%"
|
||||
"%EXE%" -manufacturer=mitsubishi -azione=Scarica -ip=%IP% -porta=%PORT% -username=%USER% -password=%PASS% -path=%PROGNUM% -pathprogramma=%RESULTS%\roundtrip_out.txt >> "%LOG%" 2>&1
|
||||
"%EXE%" -manufacturer=mitsubishi -azione=Scarica -ip=%IP% -porta=%PORT% -username=%USER% -password=%PASS% -path=%PROGNUM% -pathprogramma="%RESULTS%\roundtrip_out.txt" >> "%LOG%" 2>&1
|
||||
echo EXITCODE_scarico=%ERRORLEVEL% >> "%LOG%"
|
||||
|
||||
echo Log: %LOG%
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue