From 5769cf0f89fa4d076042e9f8dd24a32501280f8c Mon Sep 17 00:00:00 2001
From: Vadim Zeitlin <vadim@wxwidgets.org>
Date: Sat, 12 Apr 2008 17:03:09 +0000
Subject: [PATCH] fix bugs introduced in wxCmdLineParser::ConvertStringToArgs()
 during Unicode transition; added a unit test for it

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@53142 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
---
 include/wx/cppunit.h          | 16 +++++++
 src/common/cmdline.cpp        | 55 +++++++++--------------
 tests/Makefile.in             |  4 ++
 tests/cmdline/cmdlinetest.cpp | 82 +++++++++++++++++++++++++++++++++++
 tests/makefile.bcc            |  4 ++
 tests/makefile.gcc            |  4 ++
 tests/makefile.vc             |  4 ++
 tests/makefile.wat            |  4 ++
 tests/test.bkl                |  1 +
 tests/test_test.dsp           |  4 ++
 tests/test_vc7_test.vcproj    |  2 +
 tests/test_vc8_test.vcproj    |  3 ++
 12 files changed, 148 insertions(+), 35 deletions(-)
 create mode 100644 tests/cmdline/cmdlinetest.cpp

diff --git a/include/wx/cppunit.h b/include/wx/cppunit.h
index e87892b5a2..a7affd50ae 100644
--- a/include/wx/cppunit.h
+++ b/include/wx/cppunit.h
@@ -87,6 +87,22 @@
 // Use this macro to compare a size_t with a literal integer
 #define WX_ASSERT_SIZET_EQUAL(n, m) CPPUNIT_ASSERT_EQUAL(((size_t)n), m)
 
+// Use this macro to compare a wxArrayString with the pipe-separated elements
+// of the given string
+//
+// NB: it's a macro and not a function to have the correct line numbers in the
+//     test failure messages
+#define WX_ASSERT_STRARRAY_EQUAL(s, a)                                        \
+    {                                                                         \
+        wxArrayString expected(wxSplit(s, '|', '\0'));                        \
+                                                                              \
+        CPPUNIT_ASSERT_EQUAL( expected.size(), a.size() );                    \
+                                                                              \
+        for ( size_t n = 0; n < a.size(); n++ )                               \
+        {                                                                     \
+            CPPUNIT_ASSERT_EQUAL( expected[n], a[n] );                        \
+        }                                                                     \
+    }
 
 // Use this macro to assert with the given formatted message (it should contain
 // the format string and arguments in a separate pair of parentheses)
diff --git a/src/common/cmdline.cpp b/src/common/cmdline.cpp
index 63ee43c1a8..6f3b1860e5 100644
--- a/src/common/cmdline.cpp
+++ b/src/common/cmdline.cpp
@@ -1255,62 +1255,47 @@ wxArrayString wxCmdLineParser::ConvertStringToArgs(const wxString& cmdline)
 
     bool isInsideQuotes = false;
 
+    const wxString::const_iterator end = cmdline.end();
     wxString::const_iterator p = cmdline.begin();
 
     for ( ;; )
     {
         // skip white space
-        while ( p != cmdline.end() && (*p == _T(' ') || *p == _T('\t')) )
+        while ( p != end && (*p == ' ' || *p == '\t') )
             ++p;
 
         // anything left?
-        if ( p == cmdline.end() )
+        if ( p == end )
             break;
 
         // parse this parameter
-        bool endParam = false;
         bool lastBS = false;
-        for ( arg.clear(); !endParam; p++ )
+        for ( arg.clear(); p != end; ++p )
         {
-            switch ( (*p).GetValue() )
+            const wxChar ch = *p;
+            if ( ch == '"' )
             {
-                case _T('"'):
-                    if ( !lastBS )
-                    {
-                        isInsideQuotes = !isInsideQuotes;
-
-                        // don't put quote in arg
-                        continue;
-                    }
-                    //else: quote has no special meaning but the backslash
-                    //      still remains -- makes no sense but this is what
-                    //      Windows does
-                    break;
-
-                case _T(' '):
-                case _T('\t'):
-                    // backslash does *not* quote the space, only quotes do
-                    if ( isInsideQuotes )
-                    {
-                        // skip assignment below
-                        break;
-                    }
-                    // fall through
-
-                case _T('\0'):
-                    endParam = true;
+                if ( !lastBS )
+                {
+                    isInsideQuotes = !isInsideQuotes;
 
-                    break;
+                    // don't put quote in arg
+                    continue;
+                }
+                //else: quote has no special meaning but the backslash
+                //      still remains -- makes no sense but this is what
+                //      Windows does
             }
-
-            if ( endParam )
+            // note that backslash does *not* quote the space, only quotes do
+            else if ( !isInsideQuotes && (ch == ' ' || ch == '\t') )
             {
+                ++p;    // skip this space anyhow
                 break;
             }
 
-            lastBS = *p == _T('\\');
+            lastBS = ch == '\\';
 
-            arg += *p;
+            arg += ch;
         }
 
         args.push_back(arg);
diff --git a/tests/Makefile.in b/tests/Makefile.in
index 950d9703af..562680c0f7 100644
--- a/tests/Makefile.in
+++ b/tests/Makefile.in
@@ -57,6 +57,7 @@ TEST_OBJECTS =  \
 	test_tartest.o \
 	test_arrays.o \
 	test_base64.o \
+	test_cmdlinetest.o \
 	test_fileconf.o \
 	test_datetimetest.o \
 	test_filekind.o \
@@ -344,6 +345,9 @@ test_arrays.o: $(srcdir)/arrays/arrays.cpp $(TEST_ODEP)
 test_base64.o: $(srcdir)/base64/base64.cpp $(TEST_ODEP)
 	$(CXXC) -c -o $@ $(TEST_CXXFLAGS) $(srcdir)/base64/base64.cpp
 
+test_cmdlinetest.o: $(srcdir)/cmdline/cmdlinetest.cpp $(TEST_ODEP)
+	$(CXXC) -c -o $@ $(TEST_CXXFLAGS) $(srcdir)/cmdline/cmdlinetest.cpp
+
 test_fileconf.o: $(srcdir)/config/fileconf.cpp $(TEST_ODEP)
 	$(CXXC) -c -o $@ $(TEST_CXXFLAGS) $(srcdir)/config/fileconf.cpp
 
diff --git a/tests/cmdline/cmdlinetest.cpp b/tests/cmdline/cmdlinetest.cpp
new file mode 100644
index 0000000000..8e879649ae
--- /dev/null
+++ b/tests/cmdline/cmdlinetest.cpp
@@ -0,0 +1,82 @@
+///////////////////////////////////////////////////////////////////////////////
+// Name:        tests/cmdline/cmdlinetest.cpp
+// Purpose:     wxCmdLineParser unit test
+// Author:      Vadim Zeitlin
+// Created:     2008-04-12
+// RCS-ID:      $Id$
+// Copyright:   (c) 2008 Vadim Zeitlin <vadim@wxwidgets.org>
+///////////////////////////////////////////////////////////////////////////////
+
+// ----------------------------------------------------------------------------
+// headers
+// ----------------------------------------------------------------------------
+
+#include "testprec.h"
+
+#ifdef __BORLANDC__
+    #pragma hdrstop
+#endif
+
+#ifndef WX_PRECOMP
+#endif // WX_PRECOMP
+
+#include "wx/cmdline.h"
+
+// --------------------------------------------------------------------------
+// test class
+// --------------------------------------------------------------------------
+
+class CmdLineTestCase : public CppUnit::TestCase
+{
+public:
+    CmdLineTestCase() {}
+
+private:
+    CPPUNIT_TEST_SUITE( CmdLineTestCase );
+        CPPUNIT_TEST( ConvertStringTestCase );
+    CPPUNIT_TEST_SUITE_END();
+
+    void ConvertStringTestCase();
+
+    DECLARE_NO_COPY_CLASS(CmdLineTestCase)
+};
+
+// register in the unnamed registry so that these tests are run by default
+CPPUNIT_TEST_SUITE_REGISTRATION( CmdLineTestCase );
+
+// also include in it's own registry so that these tests can be run alone
+CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( CmdLineTestCase, "CmdLineTestCase" );
+
+// ============================================================================
+// implementation
+// ============================================================================
+
+void CmdLineTestCase::ConvertStringTestCase()
+{
+    #define WX_ASSERT_ARGS_EQUAL(s, args)                                     \
+        {                                                                     \
+            const wxArrayString a(wxCmdLineParser::ConvertStringToArgs(args));\
+            WX_ASSERT_STRARRAY_EQUAL(s, a);                                   \
+        }
+
+    // normal cases
+    WX_ASSERT_ARGS_EQUAL( "foo", "foo" )
+    WX_ASSERT_ARGS_EQUAL( "foo bar", "\"foo bar\"" )
+    WX_ASSERT_ARGS_EQUAL( "foo|bar", "foo bar" )
+    WX_ASSERT_ARGS_EQUAL( "foo|bar|baz", "foo bar baz" )
+    WX_ASSERT_ARGS_EQUAL( "foo|bar baz", "foo \"bar baz\"" )
+
+    // special cases
+    WX_ASSERT_ARGS_EQUAL( "", "" )
+    WX_ASSERT_ARGS_EQUAL( "foo", "foo " )
+    WX_ASSERT_ARGS_EQUAL( "foo", "foo \t   " )
+    WX_ASSERT_ARGS_EQUAL( "foo|bar", "foo bar " )
+    WX_ASSERT_ARGS_EQUAL( "foo|bar|", "foo bar \"" )
+    WX_ASSERT_ARGS_EQUAL( "foo|bar|\\", "foo bar \\" )
+
+    // check for (broken) Windows semantics: backslash doesn't escape spaces
+    WX_ASSERT_ARGS_EQUAL( "foo|bar\\|baz", "foo bar\\ baz" );
+    WX_ASSERT_ARGS_EQUAL( "foo|bar\\\"baz", "foo \"bar\\\"baz\"" );
+
+    #undef WX_ASSERT_ARGS_EQUAL
+}
diff --git a/tests/makefile.bcc b/tests/makefile.bcc
index ecf68202c7..ee35fe01e1 100644
--- a/tests/makefile.bcc
+++ b/tests/makefile.bcc
@@ -43,6 +43,7 @@ TEST_OBJECTS =  \
 	$(OBJS)\test_tartest.obj \
 	$(OBJS)\test_arrays.obj \
 	$(OBJS)\test_base64.obj \
+	$(OBJS)\test_cmdlinetest.obj \
 	$(OBJS)\test_fileconf.obj \
 	$(OBJS)\test_datetimetest.obj \
 	$(OBJS)\test_filekind.obj \
@@ -376,6 +377,9 @@ $(OBJS)\test_arrays.obj: .\arrays\arrays.cpp
 $(OBJS)\test_base64.obj: .\base64\base64.cpp
 	$(CXX) -q -c -P -o$@ $(TEST_CXXFLAGS) .\base64\base64.cpp
 
+$(OBJS)\test_cmdlinetest.obj: .\cmdline\cmdlinetest.cpp
+	$(CXX) -q -c -P -o$@ $(TEST_CXXFLAGS) .\cmdline\cmdlinetest.cpp
+
 $(OBJS)\test_fileconf.obj: .\config\fileconf.cpp
 	$(CXX) -q -c -P -o$@ $(TEST_CXXFLAGS) .\config\fileconf.cpp
 
diff --git a/tests/makefile.gcc b/tests/makefile.gcc
index 1678a05f72..49dd117b90 100644
--- a/tests/makefile.gcc
+++ b/tests/makefile.gcc
@@ -35,6 +35,7 @@ TEST_OBJECTS =  \
 	$(OBJS)\test_tartest.o \
 	$(OBJS)\test_arrays.o \
 	$(OBJS)\test_base64.o \
+	$(OBJS)\test_cmdlinetest.o \
 	$(OBJS)\test_fileconf.o \
 	$(OBJS)\test_datetimetest.o \
 	$(OBJS)\test_filekind.o \
@@ -354,6 +355,9 @@ $(OBJS)\test_arrays.o: ./arrays/arrays.cpp
 $(OBJS)\test_base64.o: ./base64/base64.cpp
 	$(CXX) -c -o $@ $(TEST_CXXFLAGS) $(CPPDEPS) $<
 
+$(OBJS)\test_cmdlinetest.o: ./cmdline/cmdlinetest.cpp
+	$(CXX) -c -o $@ $(TEST_CXXFLAGS) $(CPPDEPS) $<
+
 $(OBJS)\test_fileconf.o: ./config/fileconf.cpp
 	$(CXX) -c -o $@ $(TEST_CXXFLAGS) $(CPPDEPS) $<
 
diff --git a/tests/makefile.vc b/tests/makefile.vc
index 6477522b04..ada13d91f5 100644
--- a/tests/makefile.vc
+++ b/tests/makefile.vc
@@ -36,6 +36,7 @@ TEST_OBJECTS =  \
 	$(OBJS)\test_tartest.obj \
 	$(OBJS)\test_arrays.obj \
 	$(OBJS)\test_base64.obj \
+	$(OBJS)\test_cmdlinetest.obj \
 	$(OBJS)\test_fileconf.obj \
 	$(OBJS)\test_datetimetest.obj \
 	$(OBJS)\test_filekind.obj \
@@ -461,6 +462,9 @@ $(OBJS)\test_arrays.obj: .\arrays\arrays.cpp
 $(OBJS)\test_base64.obj: .\base64\base64.cpp
 	$(CXX) /c /nologo /TP /Fo$@ $(TEST_CXXFLAGS) .\base64\base64.cpp
 
+$(OBJS)\test_cmdlinetest.obj: .\cmdline\cmdlinetest.cpp
+	$(CXX) /c /nologo /TP /Fo$@ $(TEST_CXXFLAGS) .\cmdline\cmdlinetest.cpp
+
 $(OBJS)\test_fileconf.obj: .\config\fileconf.cpp
 	$(CXX) /c /nologo /TP /Fo$@ $(TEST_CXXFLAGS) .\config\fileconf.cpp
 
diff --git a/tests/makefile.wat b/tests/makefile.wat
index 875a094a6c..f17975db60 100644
--- a/tests/makefile.wat
+++ b/tests/makefile.wat
@@ -248,6 +248,7 @@ TEST_OBJECTS =  &
 	$(OBJS)\test_tartest.obj &
 	$(OBJS)\test_arrays.obj &
 	$(OBJS)\test_base64.obj &
+	$(OBJS)\test_cmdlinetest.obj &
 	$(OBJS)\test_fileconf.obj &
 	$(OBJS)\test_datetimetest.obj &
 	$(OBJS)\test_filekind.obj &
@@ -407,6 +408,9 @@ $(OBJS)\test_arrays.obj :  .AUTODEPEND .\arrays\arrays.cpp
 $(OBJS)\test_base64.obj :  .AUTODEPEND .\base64\base64.cpp
 	$(CXX) -bt=nt -zq -fo=$^@ $(TEST_CXXFLAGS) $<
 
+$(OBJS)\test_cmdlinetest.obj :  .AUTODEPEND .\cmdline\cmdlinetest.cpp
+	$(CXX) -bt=nt -zq -fo=$^@ $(TEST_CXXFLAGS) $<
+
 $(OBJS)\test_fileconf.obj :  .AUTODEPEND .\config\fileconf.cpp
 	$(CXX) -bt=nt -zq -fo=$^@ $(TEST_CXXFLAGS) $<
 
diff --git a/tests/test.bkl b/tests/test.bkl
index b9acc86aff..4d3d5caa3c 100644
--- a/tests/test.bkl
+++ b/tests/test.bkl
@@ -29,6 +29,7 @@
             archive/tartest.cpp
             arrays/arrays.cpp
             base64/base64.cpp
+            cmdline/cmdlinetest.cpp
             config/fileconf.cpp
             datetime/datetimetest.cpp
             filekind/filekind.cpp
diff --git a/tests/test_test.dsp b/tests/test_test.dsp
index 8191d2c212..9726938675 100644
--- a/tests/test_test.dsp
+++ b/tests/test_test.dsp
@@ -255,6 +255,10 @@ SOURCE=.\streams\bstream.cpp
 # End Source File
 # Begin Source File
 
+SOURCE=.\cmdline\cmdlinetest.cpp
+# End Source File
+# Begin Source File
+
 SOURCE=.\mbconv\convautotest.cpp
 # End Source File
 # Begin Source File
diff --git a/tests/test_vc7_test.vcproj b/tests/test_vc7_test.vcproj
index f5be6007e4..0332add381 100644
--- a/tests/test_vc7_test.vcproj
+++ b/tests/test_vc7_test.vcproj
@@ -659,6 +659,8 @@
 				RelativePath=".\base64\base64.cpp"/>
 			<File
 				RelativePath=".\streams\bstream.cpp"/>
+			<File
+				RelativePath=".\cmdline\cmdlinetest.cpp"/>
 			<File
 				RelativePath=".\mbconv\convautotest.cpp"/>
 			<File
diff --git a/tests/test_vc8_test.vcproj b/tests/test_vc8_test.vcproj
index c401ce3bda..0629254fdd 100644
--- a/tests/test_vc8_test.vcproj
+++ b/tests/test_vc8_test.vcproj
@@ -822,6 +822,9 @@
 			<File
 				RelativePath=".\streams\bstream.cpp"
 			/>
+			<File
+				RelativePath=".\cmdline\cmdlinetest.cpp"
+			/>
 			<File
 				RelativePath=".\mbconv\convautotest.cpp"
 			/>
-- 
2.47.2