Выполнение случайно переходит к выброшенному исключению при отладке модульного теста - PullRequest
2 голосов
/ 04 июля 2011

У меня очень странная проблема, когда мое выполнение переходит из полупрогнозируемых мест в другое место при отладке модульного теста Visual Studio .NET. Метод, в котором происходит это странное поведение, называется «Parse (...)» ниже. В этом методе я указал одно место, куда будет переходить выполнение ("// ИСКЛЮЧЕНИЕ"). Я также указал несколько мест, где в моем тестировании выполнение было, когда оно странным образом прыгнуло ("// JUMP"). Прыжок часто будет происходить из одного и того же места несколько раз подряд, а затем начнется последовательный прыжок с нового места. Эти места, из которых выполняются переходы, являются либо началом операторов switch, либо концом блоков кода, что наводит меня на мысль, что с указателем инструкции происходит нечто странное, но я недостаточно разбираюсь в .NET, чтобы знать, что это может быть. Если это имеет какое-либо значение, выполнение не переходит сразу к оператору throw, а вместо этого к точке выполнения, где только что было сгенерировано исключение. Очень странно.

По моему опыту, скачок выполнения происходит только при анализе содержимого вложенной именованной группы.

Справочная информация о том, что должен делать код ниже: решение, которое я пытаюсь реализовать, - это простой синтаксический анализатор регулярных выражений. Это не полный анализатор регулярных выражений. Мои потребности только в том, чтобы иметь возможность находить определенные именованные группы внутри регулярного выражения и заменять некоторые из содержимого именованных групп другим содержимым. В общем, я просто бегу через регулярные выражения и отслеживаю названные группы, которые нахожу. Я также отслеживаю неназванные группы, так как мне нужно знать о совпадении скобок и комментариях, чтобы закомментированные скобки не нарушали сопоставление пареном. Отдельный (и пока еще не реализованный) фрагмент кода восстановит строку, содержащую регулярное выражение, с учетом замен.

Я очень ценю любые предложения о том, что может быть в движении; Я сбит с толку!

Пример решения

Вот решение Visual Studio 2010 (формат TAR), содержащее весь код, который я обсуждаю ниже. У меня есть ошибка при запуске этого решения (с проектом модульного теста "TestRegexParserLibTest" в качестве проекта запуска.) Поскольку этот кажется такой спорадической ошибкой, мне было бы интересно, если кто-то еще испытывает то же самое проблема.

код

Для организации результатов я использую несколько простых классов:

// The root of the regex we are parsing
public class RegexGroupStructureRoot : ISuperRegexGroupStructure
{
    public List<RegexGroupStructure> SubStructures { get; set; }

    public RegexGroupStructureRoot()
    {
        SubStructures = new List<RegexGroupStructure>();
    }

    public override bool Equals(object obj) { ... }
}

// Either a RegexGroupStructureGroup or a RegexGroupStructureRegex
// Contained within the SubStructures of both RegexGroupStructureRoot and RegexGroupStructureGroup
public abstract class RegexGroupStructure
{
}

// A run of text containing regular expression characters (but not groups)
public class RegexGroupStructureRegex : RegexGroupStructure
{
    public string Regex { get; set; }

    public override bool Equals(object obj) { ... }
}

// A regular expression group
public class RegexGroupStructureGroup : RegexGroupStructure, ISuperRegexGroupStructure
{
    // Name == null indicates an unnamed group
    public string Name { get; set; }
    public List<RegexGroupStructure> SubStructures { get; set; }

    public RegexGroupStructureGroup()
    {
        SubStructures = new List<RegexGroupStructure>();
    }

    public override bool Equals(object obj) { ... }
}

// Items that contain SubStructures
// Either a RegexGroupStructureGroup or a RegexGroupStructureRoot
interface ISuperRegexGroupStructure
{
    List<RegexGroupStructure> SubStructures { get; }
}

Вот метод (и связанные с ним перечисления / статические члены), где я фактически анализирую регулярное выражение, возвращая RegexGroupStructureRoot, который содержит все именованные группы, неназванные группы и другие найденные символы регулярного выражения.

using Re = System.Text.RegularExpressions

enum Mode
{
    TopLevel, // Not in any group
    BeginGroup, // Just encountered a character beginning a group: "("
    BeginGroupTypeControl, // Just encountered a character controlling group type, immediately after beginning a group: "?"
    NamedGroupName, // Reading the named group name (must have encountered a character indicating a named group type immediately following a group type control character: "<" after "?")
    NamedGroup, // Reading the contents of a named group
    UnnamedGroup, // Reading the contents of an unnamed group
}

static string _NamedGroupNameValidCharRePattern = "[A-Za-z0-9_]";
static Re.Regex _NamedGroupNameValidCharRe;

static RegexGroupStructureParser()
{
    _NamedGroupNameValidCharRe = new Re.Regex(_NamedGroupNameValidCharRePattern);
}

public static RegexGroupStructureRoot Parse(string regex)
{
    string newLine = Environment.NewLine;
    int newLineLen = newLine.Length;

    // A record of the parent structures that the parser has created
    Stack<ISuperRegexGroupStructure> parentStructures = new Stack<ISuperRegexGroupStructure>();

    // The current text we've encountered
    StringBuilder textConsumer = new StringBuilder();

    // Whether the parser is in an escape sequence
    bool escaped = false;

    // Whether the parser is in an end-of-line comment (such comments run from a hash-sign ('#') to the end of the line
    //  The other type of .NET regular expression comment is the group-comment: (?#This is a comment)
    //   We do not need to specially handle this type of comment since it is treated like an unnamed
    //   group.
    bool commented = false;

    // The current mode of the parsing process
    Mode mode = Mode.TopLevel;

    // Push a root onto the parents to accept whatever regexes/groups we encounter
    parentStructures.Push(new RegexGroupStructureRoot());

    foreach (char chr in regex.ToArray())
    {
        if (escaped) // JUMP
        {
            textConsumer.Append(chr);
            escaped = false;
        }
        else if (chr.Equals('#'))
        {
            textConsumer.Append(chr);
            commented = true;
        }
        else if (commented)
        {
            textConsumer.Append(chr);

            string txt = textConsumer.ToString();
            int txtLen = txt.Length;
            if (txtLen >= newLineLen &&
                // Does the current text end with a NewLine?
                txt.Substring(txtLen - 1 - newLineLen, newLineLen) == newLine)
            {
                // If so we're no longer in the comment
                commented = false;
            }
        }
        else
        {
            switch (mode) // JUMP
            {
                case Mode.TopLevel:
                    switch (chr)
                    {
                        case '\\':
                            textConsumer.Append(chr); // Append the backslash
                            escaped = true;
                            break;
                        case '(':
                            beginNewGroup(parentStructures, ref textConsumer, ref mode);
                            break;
                        case ')':
                            // Can't close a group if we're already at the top-level
                            throw new InvalidRegexFormatException("Too many ')'s.");
                        default:
                            textConsumer.Append(chr);
                            break;
                    }
                    break;

                case Mode.BeginGroup:
                    switch (chr)
                    {
                        case '?':
                            // If it's an unnamed group, we'll re-add the question mark.
                            // If it's a named group, named groups reconstruct question marks so no need to add it.
                            mode = Mode.BeginGroupTypeControl;
                            break;
                        default:
                            // Only a '?' can begin a named group.  So anything else begins an unnamed group.

                            parentStructures.Peek().SubStructures.Add(new RegexGroupStructureRegex()
                            {
                                Regex = textConsumer.ToString()
                            });
                            textConsumer = new StringBuilder();

                            parentStructures.Push(new RegexGroupStructureGroup()
                            {
                                Name = null, // null indicates an unnamed group
                                SubStructures = new List<RegexGroupStructure>()
                            });

                            mode = Mode.UnnamedGroup;
                            break;
                    }
                    break;

                case Mode.BeginGroupTypeControl:
                    switch (chr)
                    {
                        case '<':
                            mode = Mode.NamedGroupName;
                            break;

                        default:
                            // We previously read a question mark to get here, but the group turned out not to be a named group
                            // So add back in the question mark, since unnamed groups don't reconstruct with question marks
                            textConsumer.Append('?' + chr);
                            mode = Mode.UnnamedGroup;
                            break;
                    }
                    break;

                case Mode.NamedGroupName:
                    if (chr.Equals( '>'))
                    {
                        // '>' closes the named group name.  So extract the name
                        string namedGroupName = textConsumer.ToString();

                        if (namedGroupName == String.Empty)
                            throw new InvalidRegexFormatException("Named group names cannot be empty.");

                        // Create the new named group
                        RegexGroupStructureGroup newNamedGroup = new RegexGroupStructureGroup() {
                            Name = namedGroupName,
                            SubStructures = new List<RegexGroupStructure>()
                        };

                        // Add this group to the current parent
                        parentStructures.Peek().SubStructures.Add(newNamedGroup);
                        // ...and make it the new parent.
                        parentStructures.Push(newNamedGroup);

                        textConsumer = new StringBuilder();

                        mode = Mode.NamedGroup;
                    }
                    else if (_NamedGroupNameValidCharRe.IsMatch(chr.ToString()))
                    {
                        // Append any valid named group name char to the growing named group name
                        textConsumer.Append(chr);
                    }
                    else
                    {
                        // chr is neither a valid named group name character, nor the character that closes the named group name (">").  Error.
                        throw new InvalidRegexFormatException(String.Format("Invalid named group name character: {0}", chr)); // EXCEPTION
                    }
                    break; // JUMP

                case Mode.NamedGroup:
                case Mode.UnnamedGroup:
                    switch (chr) // JUMP
                    {
                        case '\\':
                            textConsumer.Append(chr);
                            escaped = true;
                            break;
                        case ')':
                            closeGroup(parentStructures, ref textConsumer, ref mode);
                            break;
                        case '(':
                            beginNewGroup(parentStructures, ref textConsumer, ref mode);
                            break;
                        default:
                            textConsumer.Append(chr);
                            break;
                    }
                    break;

                default:
                    throw new Exception("Exhausted Modes");
            }
        } // JUMP
    }

    ISuperRegexGroupStructure finalParent = parentStructures.Pop();
    Debug.Assert(parentStructures.Count < 1, "Left parent structures on the stack.");
    Debug.Assert(finalParent.GetType().Equals(typeof(RegexGroupStructureRoot)), "The final parent must be a RegexGroupStructureRoot");

    string finalRegex = textConsumer.ToString();
    if (!String.IsNullOrEmpty(finalRegex))
        finalParent.SubStructures.Add(new RegexGroupStructureRegex() {
            Regex = finalRegex
        });

    return finalParent as RegexGroupStructureRoot;
}

А вот модульный тест, который проверит, работает ли метод (обратите внимание, что он может быть не на 100% правильным, так как я даже не могу пройти вызов RegexGroupStructureParser.Parse.)

[TestMethod]
public void ParseTest_Short()
{
    string regex = @"
        (?<Group1>
            ,?\s+
            (?<Group1_SubGroup>
                [\d–-]+             # One or more digits, hyphen, and/or n-dash
            )            
        )
    ";

    RegexGroupStructureRoot expected = new RegexGroupStructureRoot()
    {
        SubStructures = new List<RegexGroupStructure>()
        {
            new RegexGroupStructureGroup() {
                Name = "Group1", 
                SubStructures = new List<RegexGroupStructure> {
                    new RegexGroupStructureRegex() {
                        Regex = @"
            ,?\s+
            "
                    }, 
                    new RegexGroupStructureGroup() {
                        Name = "Group1_Subgroup", 
                        SubStructures = new List<RegexGroupStructure>() {
                            new RegexGroupStructureRegex() {
                                Regex = @"
                [\d–-]+             # One or more digits, hyphen, and/or n-dash
            "
                            }
                        }
                    }, 
                    new RegexGroupStructureRegex() {
                        Regex = @"            
        "
                    }
                }
            }, 
            new RegexGroupStructureRegex() {
                Regex = @"
        "
            }, 
        }
    };

    RegexGroupStructureRoot actual = RegexGroupStructureParser.Parse(regex);

    Assert.AreEqual(expected, actual);
}

Ответы [ 2 ]

1 голос
/ 08 июля 2011

Тестовый пример вашего решения вызывает , вызывая остановку исключения "Недопустимый именованный символ группы" в строке break;, а не в строке throw.Я установил тестовый файл, используя вложенный файл if, чтобы убедиться, что исключение срабатывает аналогичным образом в одном из моих проектов, а это не так: остановленная строка - это сам оператор throw.

Однако, когда я включаю редактирование (чтобы использовать редактирование и продолжить в вашем проекте), текущая строка перематывается назад на оператор throw.Я не смотрел на сгенерированный IL, но я подозреваю, что бросок (который завершит дело без необходимости «перерыва», чтобы следовать так:)

case 1:
   do something
   break;
case 2:
   throw ... //No break required.
case 3:

оптимизируется таким образом, чтобысбивает с толку отображение, но не фактическое выполнение или даже функцию редактирования и продолжения.Если редактирование и продолжение работают, и сгенерированные исключения были правильно перехвачены или отображены, я подозреваю, что у вас есть аномалия отображения, которую вы можете игнорировать (хотя я бы сообщил об этом в Microsoft вместе с этим файлом, поскольку воспроизводим ).

0 голосов
/ 28 августа 2011

Наконец-то решил этот.В closeGroup, на который есть ссылка в моем вопросе и который существует в связанном коде, я установил режим на NamedGroupName вместо NamedGroup.Это по-прежнему не отвечает на странный указатель на инструкцию / исключение, но, по крайней мере, теперь я не получаю неожиданное исключение, и анализатор анализирует.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...