Menu

#232 cctest: add automatically test for doxygen document reading

Undefined
applied
ollydbg
Feature_Request
2016-09-09
2015-10-06
ollydbg
No

As suggestion by OBF in Code::Blocks / Tickets / #224 Duplication in documentation tooltip, we may need a cctest case for the doxygen document.

Discussion

  • Teodor Petrov

    Teodor Petrov - 2016-01-19
    • Type: Undefined --> Feature_Request
     
  • White-Tiger

    White-Tiger - 2016-09-06

    I've wrote a basic implementation, but it only does substring tests, so it's not suited for a full match or duplicates. I guess it'll require regex support to handle those.

    Notes:
    1) I've also fixed some trimming while I was at it. (wxString::Trim(bool trim_right) defaults to true and cctest did two Trim().. one with default true and one with implicit true)
    2) the code inside the constructor isn't required since it's usually the default anyway... but maybe do it implicit nontheless
    3) the patch to src/plugins/codecompletion/parser/parser_base.cpp should be properly redone, or at least every use of it verified since it'll probably always default to storeDocumentation = true even in situations where it "shouldn't"
    4) the last "test" should fail... there are 2 special test values for doxygen comments, "*" as a "non-empty" wildcard, and "-" to check for empty docs

     .../codecompletion/cctest/nativeparser_test.cpp    | 86 +++++++++++++++++-----
     1 file changed, 67 insertions(+), 19 deletions(-)
    
    diff --git a/src/plugins/codecompletion/cctest/nativeparser_test.cpp b/src/plugins/codecompletion/cctest/nativeparser_test.cpp
    index 44c3059..b659657 100644
    --- a/src/plugins/codecompletion/cctest/nativeparser_test.cpp
    +++ b/src/plugins/codecompletion/cctest/nativeparser_test.cpp
    @@ -66,6 +66,8 @@ namespace CCTestAppGlobal
    
     NativeParserTest::NativeParserTest()
     {
    +    m_Parser.Options().wantPreprocessor = true; // Default
    +    m_Parser.Options().storeDocumentation = true; // Default
     }
    
     NativeParserTest::~NativeParserTest()
    @@ -235,29 +237,46 @@ bool NativeParserTest::ParseAndCodeCompletion(wxString filename, bool isLocalFil
                 // do tests here, example of line is below
                 // tc.St    //StaticVoid
                 // remove the beginning "//"
    -            str.Remove(0,2);
    +            str.Remove(0, 2);
    +            
    +            int pos;
    +            wxString expression;
    +            wxString match;
    +            wxString match_doc;
    +            
    +            // find the optional "///<" for Doxygen comment tests
    +            pos = str.Find(_T("///<"));
    +            if (pos != wxNOT_FOUND)
    +            {
    +                match_doc = str.Mid(pos + 4);
    +                str = str.Mid(0, pos);
    +            }
    
                 // find the second "//", the string after the second double slash are the
                 // the result should be listed
    -            wxString expression;
    -            wxString match;
    -            int pos = str.Find(_T("//"));
    -            if (pos == wxNOT_FOUND)
    -                break;
    -            expression = str.Mid(0,pos);
    -            match = str.Mid(pos+2);// the remaining string
    +            pos = str.Find(_T("//"));
    +            if (pos != wxNOT_FOUND)
    +            {
    +                expression = str.Mid(0, pos);
    +                match = str.Mid(pos + 2);// the remaining string
    +            }
    +            else
    +            {
    +                expression = str;
    +                if (!match_doc.IsEmpty())
    +                    match = _T("* @doxygen");
    +            }
    
    -            expression.Trim();
    -            expression.Trim(true);
    -            match.Trim();
    -            match.Trim(true);
    +            expression.Trim(true).Trim(false);
    +            match.Trim(true).Trim(false);
    +            match_doc.Trim(true).Trim(false);
    
                 wxArrayString suggestList;
                 // the match can have many items, like: AAA,BBBB
                 wxStringTokenizer tkz(match, wxT(","));
                 while ( tkz.HasMoreTokens() )
                 {
    -                wxString token = tkz.GetNextToken();
    +                wxString token = tkz.GetNextToken().Trim(true).Trim(false);
                     suggestList.Add(token);
                 }
    
    @@ -279,13 +298,42 @@ bool NativeParserTest::ParseAndCodeCompletion(wxString filename, bool isLocalFil
                         if (!token || token->m_Name.IsEmpty())
                             continue;
    
    -                    if (element.IsSameAs(token->m_Name))
    +                    if (element.IsSameAs(token->m_Name) || element[0] == '*')
                         {
    -                        message = wxString::Format(_T("+ PASS: %s  %s"), expression.wx_str(), element.wx_str());
    -                        testResult << message << wxT("\n");
    -                        wxLogMessage(message);
    -                        pass = true;
    -                        passCount++;
    +                        if (match_doc.IsEmpty())
    +                        {
    +                            message = wxString::Format(_T("+ PASS: %s  %s"), expression.wx_str(), element.wx_str());
    +                            testResult << message << wxT("\n");
    +                            wxLogMessage(message);
    +                            pass = true;
    +                            ++passCount;
    +                        }
    +                        else
    +                        {
    +                            if (token->m_Doc.Contains(match_doc)
    +                            || (match_doc[0] == '*' && match_doc.Len() == 1 && !token->m_Doc.IsEmpty())
    +                            || (match_doc[0] == '-' && match_doc.Len() == 1 && token->m_Doc.IsEmpty()))
    +                            {
    +                                message = wxString::Format(_T("+ PASS: %s  %s  \"%s\""), expression.wx_str(), token->m_Name.wx_str(), match_doc.wx_str());
    +                                testResult << message << wxT("\n");
    +                                wxLogMessage(message);
    +                                if (!pass)
    +                                {
    +                                    pass = true;
    +                                    ++passCount;
    +                                }
    +                            }
    +                            else
    +                            {
    +                                if (pass)
    +                                    --passCount;
    +                                pass = false;
    +                                element = wxString::Format(_T("%s  \"%s\""), token->m_Name.wx_str(), match_doc.wx_str());
    +                                break;
    +                            }
    +                        }
    +                        if (element[0] != '*')
    +                            break;
                         }
    
                     }
    

    But it'll only work with this patch also applied:

     src/plugins/codecompletion/parser/parser_base.cpp | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/src/plugins/codecompletion/parser/parser_base.cpp b/src/plugins/codecompletion/parser/parser_base.cpp
    index 1bcdf2e..82e1a55 100644
    --- a/src/plugins/codecompletion/parser/parser_base.cpp
    +++ b/src/plugins/codecompletion/parser/parser_base.cpp
    @@ -264,7 +264,7 @@ bool ParserBase::ParseBuffer(const wxString& buffer,
    
         opts.handleFunctions      = true;   // enabled to support function ptr in local block
    
    -    opts.storeDocumentation   = false;
    +    opts.storeDocumentation   = m_Options.storeDocumentation;
    
         ParserThread thread(this, buffer, isLocal, opts, m_TokenTree);
    

    And a test case (which currently fails all #define comments in my build...)

    #include <stdio>
    
    enum ENUM {
        VAR1 = 0, ///< Var1 \sa ENUM
        VAR2, /**< Var2 \sa ENUM */
        /** Var3 \sa ENUM */
        VAR3,
        /// Var4 \sa ENUM
        VAR4
    };
    
    #define DEF1 1 ///< Def1
    #define DEF2 1 /**< Def2 */
    /** Def3 */
    #define DEF3 1
    /// Def4
    #define DEF4 1
    
    int main(int argc, char* argv[]) {
        puts("Hello World!");
        return 0;
    }
    
    #define MASK_A  0x30000000          /*!< Mask A descr. */
    #define MASK_B  0x00070000          /*!< Mask B descr. */
    
    // VAR               ///< \sa ENUM
    // VAR1    // VAR1   ///< Var1
    // VAR2              ///< Var2
    // VAR3              ///< Var3
    // VAR4              ///< Var4
    // DEF1              ///< Def1
    // DEF2              ///< Def2
    // DEF3              ///< Def3
    // DEF4              ///< Def4
    // MASK_A            ///< Mask A descr
    // MASK_B            ///< Mask B descr
    // DEF1              ///< -
    
     
  • ollydbg

    ollydbg - 2016-09-07

    Thanks, save the test file as ccc_parse_doxygen_document.cpp, and run the tests, here is the result.

    ********************************************************
      Testing in file: F:\cb_sf_git\clean-trunk-for-commit\src\plugins\codecompletion\testing\ccc_parse_doxygen_document.cpp
    ********************************************************
    + PASS: DEF1  DEF1  "-"
    - FAIL: MASK_B  MASK_B  "Mask B descr"
    - FAIL: MASK_A  MASK_A  "Mask A descr"
    - FAIL: DEF4  DEF4  "Def4"
    - FAIL: DEF3  DEF3  "Def3"
    - FAIL: DEF2  DEF2  "Def2"
    - FAIL: DEF1  DEF1  "Def1"
    - FAIL: VAR4  VAR4  "Var4"
    - FAIL: VAR3  VAR3  "Var3"
    - FAIL: VAR2  VAR2  "Var2"
    - FAIL: VAR1  VAR1  "Var1"
    - FAIL: VAR  VAR1  "\sa ENUM"
    --------------------------------------------------------
    Total 12 tests, 1 PASS, 11 FAIL
    --------------------------------------------------------
    

    So, a "-" rule means the token don't have doxygen documents?

    EDIT: Oh, you have already stated that:

    4) the last "test" should fail... there are 2 special test values for doxygen comments, "*" as a "non-empty" wildcard, and "-" to check for empty docs

     

    Last edit: ollydbg 2016-09-07
  • ollydbg

    ollydbg - 2016-09-07

    It looks like token->m_Doc is always empty string, not sure why, let's look into the code.

    EDIT:
    it looks like m_TokenizerOptions.storeDocumentation is false when parsing the file.

     

    Last edit: ollydbg 2016-09-07
  • ollydbg

    ollydbg - 2016-09-07

    OK, found the reason:

    ParserThread::ParserThread(ParserBase*          parent,
                               const wxString&      bufferOrFilename,
                               bool                 isLocal,
                               ParserThreadOptions& parserThreadOptions,
                               TokenTree*           tokenTree) :
        m_Tokenizer(tokenTree),
        m_Parent(parent),
        m_TokenTree(tokenTree),
        m_LastParent(0),
        m_LastScope(tsUndefined),
        m_FileSize(0),
        m_FileIdx(0),
        m_IsLocal(isLocal),
        m_Options(parserThreadOptions),
        m_ParsingTypedef(false),
        m_Buffer(bufferOrFilename),
        m_StructUnionUnnamedCount(0),
        m_EnumUnnamedCount(0)
    {
        m_Tokenizer.SetTokenizerOption(parserThreadOptions.wantPreprocessor,
                                       parserThreadOptions.storeDocumentation);
        if (!m_TokenTree)
            cbThrow(_T("m_TokenTree is a nullptr?!"));
    }
    

    and

    bool ParserBase::Reparse(const wxString& file, cb_unused bool isLocal)
    {
        FileLoader* loader = new FileLoader(file);
        (*loader)();
    
        ParserThreadOptions opts;
    
        opts.useBuffer             = false; // default
        opts.parentIdxOfBuffer     = -1;    // default
        opts.initLineOfBuffer      = -1;    // default
        opts.bufferSkipBlocks      = false; // default
        opts.bufferSkipOuterBlocks = false; // default
        opts.isTemp                = false; // default
    
        opts.followLocalIncludes   = true;  // default
        opts.followGlobalIncludes  = true;  // default
        opts.wantPreprocessor      = true;  // default
        opts.parseComplexMacros    = true;  // default
        opts.platformCheck         = true;  // default
    
        opts.handleFunctions       = true;  // default
        opts.handleVars            = true;  // default
        opts.handleClasses         = true;  // default
        opts.handleEnums           = true;  // default
        opts.handleTypedefs        = true;  // default
    
        opts.loader                = loader;
    
        ParserThread* pt = new ParserThread(this, file, true, opts, m_TokenTree);
        bool success = pt->Parse();
        delete pt;
    
        return success;
    }
    

    But the ParserThreadOptions has the field storeDocumentation = false

    struct ParserThreadOptions
    {
        ParserThreadOptions() :
    
            useBuffer(false),
            fileOfBuffer(),
            parentIdxOfBuffer(-1),
            initLineOfBuffer(1),
            bufferSkipBlocks(false),
            bufferSkipOuterBlocks(false),
            isTemp(false),
    
            followLocalIncludes(true),
            followGlobalIncludes(true),
            wantPreprocessor(true),
            parseComplexMacros(true),
            platformCheck(true),
    
            handleFunctions(true),
            handleVars(true),
            handleClasses(true),
            handleEnums(true),
            handleTypedefs(true),
    
            storeDocumentation(false),
    
            loader(nullptr)
            {}
    
     
  • ollydbg

    ollydbg - 2016-09-07

    OK, now, I can see some tests are passed, see the result below:

    ********************************************************
      Testing in file: F:\cb_sf_git\clean-trunk-for-commit\src\plugins\codecompletion\testing\ccc_parse_doxygen_document.cpp
    ********************************************************
    + PASS: DEF1  DEF1  "-"
    - FAIL: MASK_B  MASK_B  "Mask B descr"
    - FAIL: MASK_A  MASK_A  "Mask A descr"
    - FAIL: DEF4  DEF4  "Def4"
    - FAIL: DEF3  DEF3  "Def3"
    - FAIL: DEF2  DEF2  "Def2"
    - FAIL: DEF1  DEF1  "Def1"
    + PASS: VAR4  VAR4  "Var4"
    + PASS: VAR3  VAR3  "Var3"
    + PASS: VAR2  VAR2  "Var2"
    + PASS: VAR1  VAR1  "Var1"
    + PASS: VAR  VAR1  "\sa ENUM"
    + PASS: VAR  VAR2  "\sa ENUM"
    + PASS: VAR  VAR3  "\sa ENUM"
    + PASS: VAR  VAR4  "\sa ENUM"
    --------------------------------------------------------
    Total 12 tests, 6 PASS, 6 FAIL
    --------------------------------------------------------
    

    With such changes

    From af1b96e8ec639907f54e137d721677289f4542a3 Mon Sep 17 00:00:00 2001
    From: asmwarrior <asmwarrior@gmail.com>
    Date: Wed, 7 Sep 2016 21:59:38 +0800
    Subject: patch3: two changes here: 1: enable the store doxygen option 2: first
     mark the file in the TokenTree, so that the doxygen document is stored in the
     Token::m_Doc, otherwise, it was wrongly stored in the m_ImpDoc. See Token*
     ParserThread::DoAddToken() about how the doxygen document are added to the
     Token.
    
    diff --git a/src/plugins/codecompletion/parser/parser_base.cpp b/src/plugins/codecompletion/parser/parser_base.cpp
    index 82e1a55..4a4641d 100644
    --- a/src/plugins/codecompletion/parser/parser_base.cpp
    +++ b/src/plugins/codecompletion/parser/parser_base.cpp
    @@ -229,8 +229,15 @@ bool ParserBase::Reparse(const wxString& file, cb_unused bool isLocal)
         opts.handleEnums           = true;  // default
         opts.handleTypedefs        = true;  // default
    
    +    opts.storeDocumentation    = true;  // enable this option to enable cctest for doxygen doc reading
    +
         opts.loader                = loader;
    
    +    // the file should first put in the TokenTree, so the index is correct when initlize the
    +    // Tokenizer object inside the ParserThread::ParserThread()
    +
    +    m_TokenTree->ReserveFileForParsing(file, true);
    +
         ParserThread* pt = new ParserThread(this, file, true, opts, m_TokenTree);
         bool success = pt->Parse();
         delete pt;
    
     
  • White-Tiger

    White-Tiger - 2016-09-07

    oh right... my patch to src/plugins/codecompletion/parser/parser_base.cpp only covered the case where manual input in cctest is used...

    Though, can't we just do it the way Code::Blocks does? (I guess it never uses ParseBuffer() but it should use files^^)
    Or what's the difference and why?

     
    • ollydbg

      ollydbg - 2016-09-07

      oh right... my patch to src/plugins/codecompletion/parser/parser_base.cpp only covered the case where manual input in cctest is used...Though, can't we just do it the way Code::Blocks does? (I guess it never uses ParseBuffer() but it should use files^^)
      Or what's the difference and why?

      The function call "m_TokenTree->ReserveFileForParsing(file, true);" follows the way the native codecompletion's parser does.

      Each file in the TokenTree has an index, so we need to call ReserveFileForParsing() first to give a file index for the file. And later, both the Parserthread and the Tokenizer will use this file index.

      For parsing the buffer, I'm not quite sure, maybe, an editor buffer does not have a file index, so it use the file index default 0.

       
  • ollydbg

    ollydbg - 2016-09-07

    OK, I found a bug that the doxygen documents are not added to the Token, because macro definitions are added in the Tokenizer class(not in the parserthread class), so, here is a simple fix:

    From 20e4325687084f3747fae52d2a0c1c2eede850ce Mon Sep 17 00:00:00 2001
    From: asmwarrior <asmwarrior@gmail.com>
    Date: Wed, 7 Sep 2016 22:35:02 +0800
    Subject: * fix a bug that we have forget adding the doxygen documents for
     macro definitions.
    
    diff --git a/src/plugins/codecompletion/parser/tokenizer.cpp b/src/plugins/codecompletion/parser/tokenizer.cpp
    index 467135a..16bdc1e 100644
    --- a/src/plugins/codecompletion/parser/tokenizer.cpp
    +++ b/src/plugins/codecompletion/parser/tokenizer.cpp
    @@ -2029,4 +2029,7 @@ void Tokenizer::AddMacroDefinition(wxString name, int line, wxString para, wxStr
         // update the definition
         token->m_Args = para;           // macro call's formal args
         token->m_FullType = substitues; // replace list
    +
    +    // this will append the doxygen style comments to the Token
    +    SetLastTokenIdx(token->m_Index);
     }
    

    After this fix, I get such results:

    ********************************************************
      Testing in file: F:\cb_sf_git\clean-trunk-for-commit\src\plugins\codecompletion\testing\ccc_parse_doxygen_document.cpp
    ********************************************************
    - FAIL: DEF1  DEF1  "-"
    + PASS: MASK_B  MASK_B  "Mask B descr"
    + PASS: MASK_A  MASK_A  "Mask A descr"
    + PASS: DEF4  DEF4  "Def4"
    + PASS: DEF3  DEF3  "Def3"
    + PASS: DEF2  DEF2  "Def2"
    + PASS: DEF1  DEF1  "Def1"
    + PASS: VAR4  VAR4  "Var4"
    + PASS: VAR3  VAR3  "Var3"
    + PASS: VAR2  VAR2  "Var2"
    + PASS: VAR1  VAR1  "Var1"
    + PASS: VAR  VAR1  "\sa ENUM"
    + PASS: VAR  VAR2  "\sa ENUM"
    + PASS: VAR  VAR3  "\sa ENUM"
    + PASS: VAR  VAR4  "\sa ENUM"
    --------------------------------------------------------
    Total 12 tests, 11 PASS, 1 FAIL
    --------------------------------------------------------
    

    This is the expect result. The 1 FAIL is also correct, because DEF1 did have doxygen documents.

    Great job, White Tiger!

     
  • ollydbg

    ollydbg - 2016-09-09
    • status: open --> applied
    • assigned_to: ollydbg
     
  • ollydbg

    ollydbg - 2016-09-09

    The modified patch is in trunk now(rev10902), Thanks!

     

Log in to post a comment.