<commit_before>// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "build/build_config.h"

#include "chrome/browser/back_forward_menu_model.h"

#include "app/l10n_util.h"
#include "chrome/browser/browser.h"
#include "chrome/browser/metrics/user_metrics.h"
#include "chrome/browser/tab_contents/navigation_controller.h"
#include "chrome/browser/tab_contents/navigation_entry.h"
#include "chrome/browser/tab_contents/tab_contents.h"
#include "chrome/common/url_constants.h"
#include "grit/generated_resources.h"
#include "net/base/registry_controlled_domain.h"

const int BackForwardMenuModel::kMaxHistoryItems = 12;
const int BackForwardMenuModel::kMaxChapterStops = 5;

int BackForwardMenuModel::GetHistoryItemCount() const {
  TabContents* contents = GetTabContents();
  int items = 0;

  if (model_type_ == FORWARD_MENU_DELEGATE) {
    // Only count items from n+1 to end (if n is current entry)
    items = contents->controller().entry_count() -
            contents->controller().GetCurrentEntryIndex() - 1;
  } else {
    items = contents->controller().GetCurrentEntryIndex();
  }

  if (items > kMaxHistoryItems)
    items = kMaxHistoryItems;
  else if (items < 0)
    items = 0;

  return items;
}

int BackForwardMenuModel::GetChapterStopCount(int history_items) const {
  TabContents* contents = GetTabContents();

  int chapter_stops = 0;
  int current_entry = contents->controller().GetCurrentEntryIndex();

  if (history_items == kMaxHistoryItems) {
    int chapter_id = current_entry;
    if (model_type_ == FORWARD_MENU_DELEGATE) {
      chapter_id += history_items;
    } else {
      chapter_id -= history_items;
    }

    do {
      chapter_id = GetIndexOfNextChapterStop(chapter_id,
          model_type_ == FORWARD_MENU_DELEGATE);
      if (chapter_id != -1)
        ++chapter_stops;
    } while (chapter_id != -1 && chapter_stops < kMaxChapterStops);
  }

  return chapter_stops;
}

int BackForwardMenuModel::GetTotalItemCount() const {
  int items = GetHistoryItemCount();

  if (items > 0) {
    int chapter_stops = 0;

    // Next, we count ChapterStops, if any.
    if (items == kMaxHistoryItems)
      chapter_stops = GetChapterStopCount(items);

    if (chapter_stops)
      items += chapter_stops + 1;  // Chapter stops also need a separator.

    // If the menu is not empty, add two positions in the end
    // for a separator and a "Show Full History" item.
    items += 2;
  }

  return items;
}

int BackForwardMenuModel::GetIndexOfNextChapterStop(int start_from,
                                                    bool forward) const {
  TabContents* contents = GetTabContents();
  NavigationController& controller = contents->controller();

  int max_count = controller.entry_count();
  if (start_from < 0 || start_from >= max_count)
    return -1;  // Out of bounds.

  if (forward) {
    if (start_from < max_count - 1) {
      // We want to advance over the current chapter stop, so we add one.
      // We don't need to do this when direction is backwards.
      start_from++;
    } else {
      return -1;
    }
  }

  NavigationEntry* start_entry = controller.GetEntryAtIndex(start_from);
  const GURL& url = start_entry->url();

  if (!forward) {
    // When going backwards we return the first entry we find that has a
    // different domain.
    for (int i = start_from - 1; i >= 0; --i) {
      if (!net::RegistryControlledDomainService::SameDomainOrHost(url,
              controller.GetEntryAtIndex(i)->url()))
        return i;
    }
    // We have reached the beginning without finding a chapter stop.
    return -1;
  } else {
    // When going forwards we return the entry before the entry that has a
    // different domain.
    for (int i = start_from + 1; i < max_count; ++i) {
      if (!net::RegistryControlledDomainService::SameDomainOrHost(url,
              controller.GetEntryAtIndex(i)->url()))
        return i - 1;
    }
    // Last entry is always considered a chapter stop.
    return max_count - 1;
  }
}

int BackForwardMenuModel::FindChapterStop(int offset,
                                          bool forward,
                                          int skip) const {
  if (offset < 0 || skip < 0)
    return -1;

  if (!forward)
    offset *= -1;

  TabContents* contents = GetTabContents();
  int entry = contents->controller().GetCurrentEntryIndex() + offset;
  for (int i = 0; i < skip + 1; i++)
    entry = GetIndexOfNextChapterStop(entry, forward);

  return entry;
}

void BackForwardMenuModel::ExecuteCommandById(int menu_id) {
  TabContents* contents = GetTabContents();
  NavigationController& controller = contents->controller();

  DCHECK(!IsSeparator(menu_id));

  // Execute the command for the last item: "Show Full History".
  if (menu_id == GetTotalItemCount()) {
    UserMetrics::RecordComputedAction(BuildActionName(L"ShowFullHistory", -1),
                                      controller.profile());
#if defined(OS_WIN)
    browser_->ShowSingleDOMUITab(GURL(chrome::kChromeUIHistoryURL));
#else
    NOTIMPLEMENTED();
#endif
    return;
  }

  // Log whether it was a history or chapter click.
  if (menu_id <= GetHistoryItemCount()) {
    UserMetrics::RecordComputedAction(
        BuildActionName(L"HistoryClick", menu_id), controller.profile());
  } else {
    UserMetrics::RecordComputedAction(
        BuildActionName(L"ChapterClick", menu_id - GetHistoryItemCount() - 1),
        controller.profile());
  }

  int index = MenuIdToNavEntryIndex(menu_id);
  if (index >= 0 && index < controller.entry_count())
    controller.GoToIndex(index);
}

bool BackForwardMenuModel::IsSeparator(int menu_id) const {
  int history_items = GetHistoryItemCount();
  // If the menu_id is higher than the number of history items + separator,
  // we then consider if it is a chapter-stop entry.
  if (menu_id > history_items + 1) {
    // We either are in ChapterStop area, or at the end of the list (the "Show
    // Full History" link).
    int chapter_stops = GetChapterStopCount(history_items);
    if (chapter_stops == 0)
      return false;  // We must have reached the "Show Full History" link.
    // Otherwise, look to see if we have reached the separator for the
    // chapter-stops. If not, this is a chapter stop.
    return (menu_id == history_items + 1 +
                       chapter_stops + 1);
  }

  // Look to see if we have reached the separator for the history items.
  return menu_id == history_items + 1;
}

std::wstring BackForwardMenuModel::GetItemLabel(int menu_id) const {
  // Return label "Show Full History" for the last item of the menu.
  if (menu_id == GetTotalItemCount())
    return l10n_util::GetString(IDS_SHOWFULLHISTORY_LINK);

  // Return an empty string for a separator.
  if (IsSeparator(menu_id))
    return L"";

  NavigationEntry* entry = GetNavigationEntry(menu_id);
  return UTF16ToWideHack(entry->GetTitleForDisplay(
                         &GetTabContents()->controller()));
}

const SkBitmap& BackForwardMenuModel::GetItemIcon(int menu_id) const {
  DCHECK(ItemHasIcon(menu_id));

  NavigationEntry* entry = GetNavigationEntry(menu_id);
  return entry->favicon().bitmap();
}

bool BackForwardMenuModel::ItemHasIcon(int menu_id) const {
  // Using "id" not "id - 1" because the last item "Show Full History"
  // doesn't have an icon.
  return menu_id < GetTotalItemCount() && !IsSeparator(menu_id);
}

bool BackForwardMenuModel::ItemHasCommand(int menu_id) const {
  return menu_id - 1 < GetTotalItemCount() && !IsSeparator(menu_id);
}

std::wstring BackForwardMenuModel::GetShowFullHistoryLabel() const {
  return l10n_util::GetString(IDS_SHOWFULLHISTORY_LINK);
}

TabContents* BackForwardMenuModel::GetTabContents() const {
  // We use the test tab contents if the unit test has specified it.
  return test_tab_contents_ ? test_tab_contents_ :
                              browser_->GetSelectedTabContents();
}

int BackForwardMenuModel::MenuIdToNavEntryIndex(int menu_id) const {
  TabContents* contents = GetTabContents();
  int history_items = GetHistoryItemCount();

  DCHECK(menu_id > 0);

  // Convert anything above the History items separator.
  if (menu_id <= history_items) {
    if (model_type_ == FORWARD_MENU_DELEGATE) {
      // The |menu_id| is relative to our current position, so we need to add.
      menu_id += contents->controller().GetCurrentEntryIndex();
    } else {
      // Back menu is reverse.
      menu_id = contents->controller().GetCurrentEntryIndex() - menu_id;
    }
    return menu_id;
  }
  if (menu_id == history_items + 1)
    return -1;           // Don't translate the separator for history items.

  if (menu_id >= history_items + 1 + GetChapterStopCount(history_items) + 1)
    return -1;           // This is beyond the last chapter stop so we abort.

  // This menu item is a chapter stop located between the two separators.
  menu_id = FindChapterStop(history_items,
                            model_type_ == FORWARD_MENU_DELEGATE,
                            menu_id - history_items - 1 - 1);

  return menu_id;
}

NavigationEntry* BackForwardMenuModel::GetNavigationEntry(int menu_id) const {
  int index = MenuIdToNavEntryIndex(menu_id);
  return GetTabContents()->controller().GetEntryAtIndex(index);
}

std::wstring BackForwardMenuModel::BuildActionName(
    const std::wstring& action, int index) const {
  DCHECK(!action.empty());
  DCHECK(index >= -1);
  std::wstring metric_string;
  if (model_type_ == FORWARD_MENU_DELEGATE)
    metric_string += L"ForwardMenu_";
  else
    metric_string += L"BackMenu_";
  metric_string += action;
  if (index != -1)
    metric_string += IntToWString(index);
  return metric_string;
}
<commit_msg>Linux and Mac support chrome://history by now. Remove NOTIMPLEMENTED.<commit_after>// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "build/build_config.h"

#include "chrome/browser/back_forward_menu_model.h"

#include "app/l10n_util.h"
#include "chrome/browser/browser.h"
#include "chrome/browser/metrics/user_metrics.h"
#include "chrome/browser/tab_contents/navigation_controller.h"
#include "chrome/browser/tab_contents/navigation_entry.h"
#include "chrome/browser/tab_contents/tab_contents.h"
#include "chrome/common/url_constants.h"
#include "grit/generated_resources.h"
#include "net/base/registry_controlled_domain.h"

const int BackForwardMenuModel::kMaxHistoryItems = 12;
const int BackForwardMenuModel::kMaxChapterStops = 5;

int BackForwardMenuModel::GetHistoryItemCount() const {
  TabContents* contents = GetTabContents();
  int items = 0;

  if (model_type_ == FORWARD_MENU_DELEGATE) {
    // Only count items from n+1 to end (if n is current entry)
    items = contents->controller().entry_count() -
            contents->controller().GetCurrentEntryIndex() - 1;
  } else {
    items = contents->controller().GetCurrentEntryIndex();
  }

  if (items > kMaxHistoryItems)
    items = kMaxHistoryItems;
  else if (items < 0)
    items = 0;

  return items;
}

int BackForwardMenuModel::GetChapterStopCount(int history_items) const {
  TabContents* contents = GetTabContents();

  int chapter_stops = 0;
  int current_entry = contents->controller().GetCurrentEntryIndex();

  if (history_items == kMaxHistoryItems) {
    int chapter_id = current_entry;
    if (model_type_ == FORWARD_MENU_DELEGATE) {
      chapter_id += history_items;
    } else {
      chapter_id -= history_items;
    }

    do {
      chapter_id = GetIndexOfNextChapterStop(chapter_id,
          model_type_ == FORWARD_MENU_DELEGATE);
      if (chapter_id != -1)
        ++chapter_stops;
    } while (chapter_id != -1 && chapter_stops < kMaxChapterStops);
  }

  return chapter_stops;
}

int BackForwardMenuModel::GetTotalItemCount() const {
  int items = GetHistoryItemCount();

  if (items > 0) {
    int chapter_stops = 0;

    // Next, we count ChapterStops, if any.
    if (items == kMaxHistoryItems)
      chapter_stops = GetChapterStopCount(items);

    if (chapter_stops)
      items += chapter_stops + 1;  // Chapter stops also need a separator.

    // If the menu is not empty, add two positions in the end
    // for a separator and a "Show Full History" item.
    items += 2;
  }

  return items;
}

int BackForwardMenuModel::GetIndexOfNextChapterStop(int start_from,
                                                    bool forward) const {
  TabContents* contents = GetTabContents();
  NavigationController& controller = contents->controller();

  int max_count = controller.entry_count();
  if (start_from < 0 || start_from >= max_count)
    return -1;  // Out of bounds.

  if (forward) {
    if (start_from < max_count - 1) {
      // We want to advance over the current chapter stop, so we add one.
      // We don't need to do this when direction is backwards.
      start_from++;
    } else {
      return -1;
    }
  }

  NavigationEntry* start_entry = controller.GetEntryAtIndex(start_from);
  const GURL& url = start_entry->url();

  if (!forward) {
    // When going backwards we return the first entry we find that has a
    // different domain.
    for (int i = start_from - 1; i >= 0; --i) {
      if (!net::RegistryControlledDomainService::SameDomainOrHost(url,
              controller.GetEntryAtIndex(i)->url()))
        return i;
    }
    // We have reached the beginning without finding a chapter stop.
    return -1;
  } else {
    // When going forwards we return the entry before the entry that has a
    // different domain.
    for (int i = start_from + 1; i < max_count; ++i) {
      if (!net::RegistryControlledDomainService::SameDomainOrHost(url,
              controller.GetEntryAtIndex(i)->url()))
        return i - 1;
    }
    // Last entry is always considered a chapter stop.
    return max_count - 1;
  }
}

int BackForwardMenuModel::FindChapterStop(int offset,
                                          bool forward,
                                          int skip) const {
  if (offset < 0 || skip < 0)
    return -1;

  if (!forward)
    offset *= -1;

  TabContents* contents = GetTabContents();
  int entry = contents->controller().GetCurrentEntryIndex() + offset;
  for (int i = 0; i < skip + 1; i++)
    entry = GetIndexOfNextChapterStop(entry, forward);

  return entry;
}

void BackForwardMenuModel::ExecuteCommandById(int menu_id) {
  TabContents* contents = GetTabContents();
  NavigationController& controller = contents->controller();

  DCHECK(!IsSeparator(menu_id));

  // Execute the command for the last item: "Show Full History".
  if (menu_id == GetTotalItemCount()) {
    UserMetrics::RecordComputedAction(BuildActionName(L"ShowFullHistory", -1),
                                      controller.profile());
    browser_->ShowSingleDOMUITab(GURL(chrome::kChromeUIHistoryURL));
    return;
  }

  // Log whether it was a history or chapter click.
  if (menu_id <= GetHistoryItemCount()) {
    UserMetrics::RecordComputedAction(
        BuildActionName(L"HistoryClick", menu_id), controller.profile());
  } else {
    UserMetrics::RecordComputedAction(
        BuildActionName(L"ChapterClick", menu_id - GetHistoryItemCount() - 1),
        controller.profile());
  }

  int index = MenuIdToNavEntryIndex(menu_id);
  if (index >= 0 && index < controller.entry_count())
    controller.GoToIndex(index);
}

bool BackForwardMenuModel::IsSeparator(int menu_id) const {
  int history_items = GetHistoryItemCount();
  // If the menu_id is higher than the number of history items + separator,
  // we then consider if it is a chapter-stop entry.
  if (menu_id > history_items + 1) {
    // We either are in ChapterStop area, or at the end of the list (the "Show
    // Full History" link).
    int chapter_stops = GetChapterStopCount(history_items);
    if (chapter_stops == 0)
      return false;  // We must have reached the "Show Full History" link.
    // Otherwise, look to see if we have reached the separator for the
    // chapter-stops. If not, this is a chapter stop.
    return (menu_id == history_items + 1 +
                       chapter_stops + 1);
  }

  // Look to see if we have reached the separator for the history items.
  return menu_id == history_items + 1;
}

std::wstring BackForwardMenuModel::GetItemLabel(int menu_id) const {
  // Return label "Show Full History" for the last item of the menu.
  if (menu_id == GetTotalItemCount())
    return l10n_util::GetString(IDS_SHOWFULLHISTORY_LINK);

  // Return an empty string for a separator.
  if (IsSeparator(menu_id))
    return L"";

  NavigationEntry* entry = GetNavigationEntry(menu_id);
  return UTF16ToWideHack(entry->GetTitleForDisplay(
                         &GetTabContents()->controller()));
}

const SkBitmap& BackForwardMenuModel::GetItemIcon(int menu_id) const {
  DCHECK(ItemHasIcon(menu_id));

  NavigationEntry* entry = GetNavigationEntry(menu_id);
  return entry->favicon().bitmap();
}

bool BackForwardMenuModel::ItemHasIcon(int menu_id) const {
  // Using "id" not "id - 1" because the last item "Show Full History"
  // doesn't have an icon.
  return menu_id < GetTotalItemCount() && !IsSeparator(menu_id);
}

bool BackForwardMenuModel::ItemHasCommand(int menu_id) const {
  return menu_id - 1 < GetTotalItemCount() && !IsSeparator(menu_id);
}

std::wstring BackForwardMenuModel::GetShowFullHistoryLabel() const {
  return l10n_util::GetString(IDS_SHOWFULLHISTORY_LINK);
}

TabContents* BackForwardMenuModel::GetTabContents() const {
  // We use the test tab contents if the unit test has specified it.
  return test_tab_contents_ ? test_tab_contents_ :
                              browser_->GetSelectedTabContents();
}

int BackForwardMenuModel::MenuIdToNavEntryIndex(int menu_id) const {
  TabContents* contents = GetTabContents();
  int history_items = GetHistoryItemCount();

  DCHECK(menu_id > 0);

  // Convert anything above the History items separator.
  if (menu_id <= history_items) {
    if (model_type_ == FORWARD_MENU_DELEGATE) {
      // The |menu_id| is relative to our current position, so we need to add.
      menu_id += contents->controller().GetCurrentEntryIndex();
    } else {
      // Back menu is reverse.
      menu_id = contents->controller().GetCurrentEntryIndex() - menu_id;
    }
    return menu_id;
  }
  if (menu_id == history_items + 1)
    return -1;           // Don't translate the separator for history items.

  if (menu_id >= history_items + 1 + GetChapterStopCount(history_items) + 1)
    return -1;           // This is beyond the last chapter stop so we abort.

  // This menu item is a chapter stop located between the two separators.
  menu_id = FindChapterStop(history_items,
                            model_type_ == FORWARD_MENU_DELEGATE,
                            menu_id - history_items - 1 - 1);

  return menu_id;
}

NavigationEntry* BackForwardMenuModel::GetNavigationEntry(int menu_id) const {
  int index = MenuIdToNavEntryIndex(menu_id);
  return GetTabContents()->controller().GetEntryAtIndex(index);
}

std::wstring BackForwardMenuModel::BuildActionName(
    const std::wstring& action, int index) const {
  DCHECK(!action.empty());
  DCHECK(index >= -1);
  std::wstring metric_string;
  if (model_type_ == FORWARD_MENU_DELEGATE)
    metric_string += L"ForwardMenu_";
  else
    metric_string += L"BackMenu_";
  metric_string += action;
  if (index != -1)
    metric_string += IntToWString(index);
  return metric_string;
}
<|endoftext|>


<commit_before><commit_msg>Clear yet another few stats when a new chrome version is installed<commit_after><|endoftext|>


<commit_before>/****************************************************************************
**
** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** No Commercial Usage
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the either Technology Preview License Agreement or the
** Beta Release License Agreement.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain
** additional rights. These rights are described in the Nokia Qt LGPL
** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
** package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://www.qtsoftware.com/contact.
** $QT_END_LICENSE$
**
****************************************************************************/

#include <QtTest/QtTest>
#include <QTextDocument>
#include <QTextLayout>
#include <QDebug>
#include <QAbstractTextDocumentLayout>
#include <QSyntaxHighlighter>

//TESTED_CLASS=
//TESTED_FILES=
//
class QTestDocumentLayout : public QAbstractTextDocumentLayout
{
    Q_OBJECT
public:
    inline QTestDocumentLayout(QTextDocument *doc)
        : QAbstractTextDocumentLayout(doc), documentChangedCalled(false) {}

        virtual void draw(QPainter *, const QAbstractTextDocumentLayout::PaintContext &)  {}

        virtual int hitTest(const QPointF &, Qt::HitTestAccuracy ) const { return 0; }

        virtual void documentChanged(int, int, int) { documentChangedCalled = true; }

        virtual int pageCount() const { return 1; }

        virtual QSizeF documentSize() const { return QSize(); }

        virtual QRectF frameBoundingRect(QTextFrame *) const { return QRectF(); }
        virtual QRectF blockBoundingRect(const QTextBlock &) const { return QRectF(); }

        bool documentChangedCalled;
};

class tst_QSyntaxHighlighter : public QObject
{
    Q_OBJECT
public:
    inline tst_QSyntaxHighlighter() {}

public slots:
    void init();
    void cleanup();

private slots:
    void basic();
    void basicTwo();
    void removeFormatsOnDelete();
    void emptyBlocks();
    void setCharFormat();
    void highlightOnInit();
    void stopHighlightingWhenStateDoesNotChange();
    void unindent();
    void highlightToEndOfDocument();
    void highlightToEndOfDocument2();
    void preservePreeditArea();
    void task108530();
    void avoidUnnecessaryRehighlight();
    void noContentsChangedDuringHighlight();
    void rehighlight();

private:
    QTextDocument *doc;
    QTestDocumentLayout *lout;
    QTextCursor cursor;
};

void tst_QSyntaxHighlighter::init()
{
    doc = new QTextDocument;
    lout = new QTestDocumentLayout(doc);
    doc->setDocumentLayout(lout);
    cursor = QTextCursor(doc);
}

void tst_QSyntaxHighlighter::cleanup()
{
    delete doc;
    doc = 0;
}

class TestHighlighter : public QSyntaxHighlighter
{
public:
    inline TestHighlighter(const QList<QTextLayout::FormatRange> &fmts, QTextDocument *parent)
        : QSyntaxHighlighter(parent), formats(fmts), highlighted(false), callCount(0) {}
        inline TestHighlighter(QTextDocument *parent)
            : QSyntaxHighlighter(parent), highlighted(false), callCount(0) {}

            virtual void highlightBlock(const QString &text)
            {
                for (int i = 0; i < formats.count(); ++i) {
                    const QTextLayout::FormatRange &range = formats.at(i);
                    setFormat(range.start, range.length, range.format);
                }
                highlighted = true;
                highlightedText += text;
                ++callCount;
            }

            QList<QTextLayout::FormatRange> formats;
            bool highlighted;
            int callCount;
            QString highlightedText;
};

QT_BEGIN_NAMESPACE
static bool operator==(const QTextLayout::FormatRange &lhs, const QTextLayout::FormatRange &rhs)
{
    return lhs.start == rhs.start
        && lhs.length == rhs.length
        && lhs.format == rhs.format;
}
QT_END_NAMESPACE

void tst_QSyntaxHighlighter::basic()
{
    QList<QTextLayout::FormatRange> formats;
    QTextLayout::FormatRange range;
    range.start = 0;
    range.length = 2;
    range.format.setForeground(Qt::blue);
    formats.append(range);

    range.start = 4;
    range.length = 2;
    range.format.setFontItalic(true);
    formats.append(range);

    range.start = 9;
    range.length = 2;
    range.format.setFontUnderline(true);
    formats.append(range);

    TestHighlighter *hl = new TestHighlighter(formats, doc);

    lout->documentChangedCalled = false;
    doc->setPlainText("Hello World");
    QVERIFY(hl->highlighted);
    QVERIFY(lout->documentChangedCalled);

    QVERIFY(doc->begin().layout()->additionalFormats() == formats);
}

class CommentTestHighlighter : public QSyntaxHighlighter
{
public:
    inline CommentTestHighlighter(QTextDocument *parent)
        : QSyntaxHighlighter(parent), highlighted(false) {}

        inline void reset()
        {
            highlighted = false;
        }

        virtual void highlightBlock(const QString &text)
        {
            QTextCharFormat commentFormat;
            commentFormat.setForeground(Qt::darkGreen);
            commentFormat.setFontWeight(QFont::StyleItalic);
            commentFormat.setFontFixedPitch(true);
            int textLength = text.length();

            if (text.startsWith(QLatin1Char(';'))){
                // The entire line is a comment
                setFormat(0, textLength, commentFormat);
                highlighted = true;
            }
        }
        bool highlighted;
};

void tst_QSyntaxHighlighter::basicTwo()
{
    // Done for task 104409
    CommentTestHighlighter *hl = new CommentTestHighlighter(doc);
    doc->setPlainText("; a test");
    QVERIFY(hl->highlighted);
    QVERIFY(lout->documentChangedCalled);
}

void tst_QSyntaxHighlighter::removeFormatsOnDelete()
{
    QList<QTextLayout::FormatRange> formats;
    QTextLayout::FormatRange range;
    range.start = 0;
    range.length = 9;
    range.format.setForeground(Qt::blue);
    formats.append(range);

    TestHighlighter *hl = new TestHighlighter(formats, doc);

    lout->documentChangedCalled = false;
    doc->setPlainText("Hello World");
    QVERIFY(hl->highlighted);
    QVERIFY(lout->documentChangedCalled);

    lout->documentChangedCalled = false;
    QVERIFY(!doc->begin().layout()->additionalFormats().isEmpty());
    delete hl;
    QVERIFY(doc->begin().layout()->additionalFormats().isEmpty());
    QVERIFY(lout->documentChangedCalled);
}

void tst_QSyntaxHighlighter::emptyBlocks()
{
    TestHighlighter *hl = new TestHighlighter(doc);

    cursor.insertText("Foo");
    cursor.insertBlock();
    cursor.insertBlock();
    hl->highlighted = false;
    cursor.insertBlock();
    QVERIFY(hl->highlighted);
}

void tst_QSyntaxHighlighter::setCharFormat()
{
    TestHighlighter *hl = new TestHighlighter(doc);

    cursor.insertText("FooBar");
    cursor.insertBlock();
    cursor.insertText("Blah");
    cursor.movePosition(QTextCursor::Start);
    cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
    QTextCharFormat fmt;
    fmt.setFontItalic(true);
    hl->highlighted = false;
    hl->callCount = 0;
    cursor.mergeCharFormat(fmt);
    QVERIFY(hl->highlighted);
    QCOMPARE(hl->callCount, 2);
}

void tst_QSyntaxHighlighter::highlightOnInit()
{
    cursor.insertText("Hello");
    cursor.insertBlock();
    cursor.insertText("World");

    TestHighlighter *hl = new TestHighlighter(doc);
    QTest::qWait(100);
    QVERIFY(hl->highlighted);
}

class StateTestHighlighter : public QSyntaxHighlighter
{
public:
    inline StateTestHighlighter(QTextDocument *parent)
        : QSyntaxHighlighter(parent), state(0), highlighted(false) {}

        inline void reset()
        {
            highlighted = false;
            state = 0;
        }

        virtual void highlightBlock(const QString &text)
        {
            highlighted = true;
            if (text == QLatin1String("changestate"))
                setCurrentBlockState(state++);
        }

        int state;
        bool highlighted;
};

void tst_QSyntaxHighlighter::stopHighlightingWhenStateDoesNotChange()
{
    cursor.insertText("state");
    cursor.insertBlock();
    cursor.insertText("changestate");
    cursor.insertBlock();
    cursor.insertText("keepstate");
    cursor.insertBlock();
    cursor.insertText("changestate");
    cursor.insertBlock();
    cursor.insertText("changestate");

    StateTestHighlighter *hl = new StateTestHighlighter(doc);
    QTest::qWait(100);
    QVERIFY(hl->highlighted);

    hl->reset();

    // turn the text of the first block into 'changestate'
    cursor.movePosition(QTextCursor::Start);
    cursor.insertText("change");

    // verify that we highlighted only to the 'keepstate' block,
    // not beyond
    QCOMPARE(hl->state, 2);
}

void tst_QSyntaxHighlighter::unindent()
{
    const QString spaces("    ");
    const QString text("Foobar");
    QString plainText;
    for (int i = 0; i < 5; ++i) {
        cursor.insertText(spaces + text);
        cursor.insertBlock();

        plainText += spaces;
        plainText += text;
        plainText += QLatin1Char('\n');
    }
    QCOMPARE(doc->toPlainText(), plainText);

    TestHighlighter *hl = new TestHighlighter(doc);
    hl->callCount = 0;

    cursor.movePosition(QTextCursor::Start);
    cursor.beginEditBlock();

    plainText.clear();
    for (int i = 0; i < 5; ++i) {
        cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, 4);
        cursor.removeSelectedText();
        cursor.movePosition(QTextCursor::NextBlock);

        plainText += text;
        plainText += QLatin1Char('\n');
    }

    cursor.endEditBlock();
    QCOMPARE(doc->toPlainText(), plainText);
    QCOMPARE(hl->callCount, 5);
}

void tst_QSyntaxHighlighter::highlightToEndOfDocument()
{
    TestHighlighter *hl = new TestHighlighter(doc);
    hl->callCount = 0;

    cursor.movePosition(QTextCursor::Start);
    cursor.beginEditBlock();

    cursor.insertText("Hello");
    cursor.insertBlock();
    cursor.insertBlock();
    cursor.insertText("World");
    cursor.insertBlock();

    cursor.endEditBlock();

    QCOMPARE(hl->callCount, 4);
}

void tst_QSyntaxHighlighter::highlightToEndOfDocument2()
{
    TestHighlighter *hl = new TestHighlighter(doc);
    hl->callCount = 0;

    cursor.movePosition(QTextCursor::End);
    cursor.beginEditBlock();
    QTextBlockFormat fmt;
    fmt.setAlignment(Qt::AlignLeft);
    cursor.setBlockFormat(fmt);
    cursor.insertText("Three\nLines\nHere");
    cursor.endEditBlock();

    QCOMPARE(hl->callCount, 3);
}

void tst_QSyntaxHighlighter::preservePreeditArea()
{
    QList<QTextLayout::FormatRange> formats;
    QTextLayout::FormatRange range;
    range.start = 0;
    range.length = 8;
    range.format.setForeground(Qt::blue);
    formats << range;
    range.start = 9;
    range.length = 1;
    range.format.setForeground(Qt::red);
    formats << range;
    TestHighlighter *hl = new TestHighlighter(formats, doc);

    doc->setPlainText("Hello World");
    cursor.movePosition(QTextCursor::Start);

    QTextLayout *layout = cursor.block().layout();

    layout->setPreeditArea(5, QString("foo"));
    range.start = 5;
    range.length = 3;
    range.format.setFontUnderline(true);
    formats.clear();
    formats << range;

    hl->callCount = 0;

    cursor.beginEditBlock();
    layout->setAdditionalFormats(formats);
    cursor.endEditBlock();

    QCOMPARE(hl->callCount, 1);

    formats = layout->additionalFormats();
    QCOMPARE(formats.count(), 3);

    range = formats.at(0);

    QCOMPARE(range.start, 5);
    QCOMPARE(range.length, 3);
    QVERIFY(range.format.fontUnderline());

    range = formats.at(1);
    QCOMPARE(range.start, 0);
    QCOMPARE(range.length, 8 + 3);

    range = formats.at(2);
    QCOMPARE(range.start, 9 + 3);
    QCOMPARE(range.length, 1);
}

void tst_QSyntaxHighlighter::task108530()
{
    TestHighlighter *hl = new TestHighlighter(doc);

    cursor.insertText("test");
    hl->callCount = 0;
    hl->highlightedText.clear();
    cursor.movePosition(QTextCursor::Start);
    cursor.insertBlock();

    QCOMPARE(hl->highlightedText, QString("test"));
    QCOMPARE(hl->callCount, 2);
}

void tst_QSyntaxHighlighter::avoidUnnecessaryRehighlight()
{
    TestHighlighter *hl = new TestHighlighter(doc);
    QVERIFY(!hl->highlighted);

    doc->setPlainText("Hello World");
    QVERIFY(hl->highlighted);

    hl->highlighted = false;
    QTest::qWait(100);
    QVERIFY(!hl->highlighted);
}

void tst_QSyntaxHighlighter::noContentsChangedDuringHighlight()
{
    QList<QTextLayout::FormatRange> formats;
    QTextLayout::FormatRange range;
    range.start = 0;
    range.length = 10;
    range.format.setForeground(Qt::blue);
    formats.append(range);

    TestHighlighter *hl = new TestHighlighter(formats, doc);

    lout->documentChangedCalled = false;
    QTextCursor cursor(doc);

    QSignalSpy contentsChangedSpy(doc, SIGNAL(contentsChanged()));
    cursor.insertText("Hello World");

    QCOMPARE(contentsChangedSpy.count(), 1);
    QVERIFY(hl->highlighted);
    QVERIFY(lout->documentChangedCalled);
}

void tst_QSyntaxHighlighter::rehighlight()
{
    TestHighlighter *hl = new TestHighlighter(doc);
    hl->callCount = 0;
    doc->setPlainText("Hello");
    hl->callCount = 0;
    hl->rehighlight();
    QCOMPARE(hl->callCount, 1);
}

QTEST_MAIN(tst_QSyntaxHighlighter)
#include "tst_qsyntaxhighlighter.moc"
<commit_msg>Added QSyntaxHighlighter::rehighlightBlock() auto test<commit_after>/****************************************************************************
**
** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** No Commercial Usage
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the either Technology Preview License Agreement or the
** Beta Release License Agreement.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain
** additional rights. These rights are described in the Nokia Qt LGPL
** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
** package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://www.qtsoftware.com/contact.
** $QT_END_LICENSE$
**
****************************************************************************/

#include <QtTest/QtTest>
#include <QTextDocument>
#include <QTextLayout>
#include <QDebug>
#include <QAbstractTextDocumentLayout>
#include <QSyntaxHighlighter>

//TESTED_CLASS=
//TESTED_FILES=
//
class QTestDocumentLayout : public QAbstractTextDocumentLayout
{
    Q_OBJECT
public:
    inline QTestDocumentLayout(QTextDocument *doc)
        : QAbstractTextDocumentLayout(doc), documentChangedCalled(false) {}

        virtual void draw(QPainter *, const QAbstractTextDocumentLayout::PaintContext &)  {}

        virtual int hitTest(const QPointF &, Qt::HitTestAccuracy ) const { return 0; }

        virtual void documentChanged(int, int, int) { documentChangedCalled = true; }

        virtual int pageCount() const { return 1; }

        virtual QSizeF documentSize() const { return QSize(); }

        virtual QRectF frameBoundingRect(QTextFrame *) const { return QRectF(); }
        virtual QRectF blockBoundingRect(const QTextBlock &) const { return QRectF(); }

        bool documentChangedCalled;
};

class tst_QSyntaxHighlighter : public QObject
{
    Q_OBJECT
public:
    inline tst_QSyntaxHighlighter() {}

public slots:
    void init();
    void cleanup();

private slots:
    void basic();
    void basicTwo();
    void removeFormatsOnDelete();
    void emptyBlocks();
    void setCharFormat();
    void highlightOnInit();
    void stopHighlightingWhenStateDoesNotChange();
    void unindent();
    void highlightToEndOfDocument();
    void highlightToEndOfDocument2();
    void preservePreeditArea();
    void task108530();
    void avoidUnnecessaryRehighlight();
    void noContentsChangedDuringHighlight();
    void rehighlight();
    void rehighlightBlock();

private:
    QTextDocument *doc;
    QTestDocumentLayout *lout;
    QTextCursor cursor;
};

void tst_QSyntaxHighlighter::init()
{
    doc = new QTextDocument;
    lout = new QTestDocumentLayout(doc);
    doc->setDocumentLayout(lout);
    cursor = QTextCursor(doc);
}

void tst_QSyntaxHighlighter::cleanup()
{
    delete doc;
    doc = 0;
}

class TestHighlighter : public QSyntaxHighlighter
{
public:
    inline TestHighlighter(const QList<QTextLayout::FormatRange> &fmts, QTextDocument *parent)
        : QSyntaxHighlighter(parent), formats(fmts), highlighted(false), callCount(0) {}
        inline TestHighlighter(QTextDocument *parent)
            : QSyntaxHighlighter(parent), highlighted(false), callCount(0) {}

            virtual void highlightBlock(const QString &text)
            {
                for (int i = 0; i < formats.count(); ++i) {
                    const QTextLayout::FormatRange &range = formats.at(i);
                    setFormat(range.start, range.length, range.format);
                }
                highlighted = true;
                highlightedText += text;
                ++callCount;
            }

            QList<QTextLayout::FormatRange> formats;
            bool highlighted;
            int callCount;
            QString highlightedText;
};

QT_BEGIN_NAMESPACE
static bool operator==(const QTextLayout::FormatRange &lhs, const QTextLayout::FormatRange &rhs)
{
    return lhs.start == rhs.start
        && lhs.length == rhs.length
        && lhs.format == rhs.format;
}
QT_END_NAMESPACE

void tst_QSyntaxHighlighter::basic()
{
    QList<QTextLayout::FormatRange> formats;
    QTextLayout::FormatRange range;
    range.start = 0;
    range.length = 2;
    range.format.setForeground(Qt::blue);
    formats.append(range);

    range.start = 4;
    range.length = 2;
    range.format.setFontItalic(true);
    formats.append(range);

    range.start = 9;
    range.length = 2;
    range.format.setFontUnderline(true);
    formats.append(range);

    TestHighlighter *hl = new TestHighlighter(formats, doc);

    lout->documentChangedCalled = false;
    doc->setPlainText("Hello World");
    QVERIFY(hl->highlighted);
    QVERIFY(lout->documentChangedCalled);

    QVERIFY(doc->begin().layout()->additionalFormats() == formats);
}

class CommentTestHighlighter : public QSyntaxHighlighter
{
public:
    inline CommentTestHighlighter(QTextDocument *parent)
        : QSyntaxHighlighter(parent), highlighted(false) {}

        inline void reset()
        {
            highlighted = false;
        }

        virtual void highlightBlock(const QString &text)
        {
            QTextCharFormat commentFormat;
            commentFormat.setForeground(Qt::darkGreen);
            commentFormat.setFontWeight(QFont::StyleItalic);
            commentFormat.setFontFixedPitch(true);
            int textLength = text.length();

            if (text.startsWith(QLatin1Char(';'))){
                // The entire line is a comment
                setFormat(0, textLength, commentFormat);
                highlighted = true;
            }
        }
        bool highlighted;
};

void tst_QSyntaxHighlighter::basicTwo()
{
    // Done for task 104409
    CommentTestHighlighter *hl = new CommentTestHighlighter(doc);
    doc->setPlainText("; a test");
    QVERIFY(hl->highlighted);
    QVERIFY(lout->documentChangedCalled);
}

void tst_QSyntaxHighlighter::removeFormatsOnDelete()
{
    QList<QTextLayout::FormatRange> formats;
    QTextLayout::FormatRange range;
    range.start = 0;
    range.length = 9;
    range.format.setForeground(Qt::blue);
    formats.append(range);

    TestHighlighter *hl = new TestHighlighter(formats, doc);

    lout->documentChangedCalled = false;
    doc->setPlainText("Hello World");
    QVERIFY(hl->highlighted);
    QVERIFY(lout->documentChangedCalled);

    lout->documentChangedCalled = false;
    QVERIFY(!doc->begin().layout()->additionalFormats().isEmpty());
    delete hl;
    QVERIFY(doc->begin().layout()->additionalFormats().isEmpty());
    QVERIFY(lout->documentChangedCalled);
}

void tst_QSyntaxHighlighter::emptyBlocks()
{
    TestHighlighter *hl = new TestHighlighter(doc);

    cursor.insertText("Foo");
    cursor.insertBlock();
    cursor.insertBlock();
    hl->highlighted = false;
    cursor.insertBlock();
    QVERIFY(hl->highlighted);
}

void tst_QSyntaxHighlighter::setCharFormat()
{
    TestHighlighter *hl = new TestHighlighter(doc);

    cursor.insertText("FooBar");
    cursor.insertBlock();
    cursor.insertText("Blah");
    cursor.movePosition(QTextCursor::Start);
    cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
    QTextCharFormat fmt;
    fmt.setFontItalic(true);
    hl->highlighted = false;
    hl->callCount = 0;
    cursor.mergeCharFormat(fmt);
    QVERIFY(hl->highlighted);
    QCOMPARE(hl->callCount, 2);
}

void tst_QSyntaxHighlighter::highlightOnInit()
{
    cursor.insertText("Hello");
    cursor.insertBlock();
    cursor.insertText("World");

    TestHighlighter *hl = new TestHighlighter(doc);
    QTest::qWait(100);
    QVERIFY(hl->highlighted);
}

class StateTestHighlighter : public QSyntaxHighlighter
{
public:
    inline StateTestHighlighter(QTextDocument *parent)
        : QSyntaxHighlighter(parent), state(0), highlighted(false) {}

        inline void reset()
        {
            highlighted = false;
            state = 0;
        }

        virtual void highlightBlock(const QString &text)
        {
            highlighted = true;
            if (text == QLatin1String("changestate"))
                setCurrentBlockState(state++);
        }

        int state;
        bool highlighted;
};

void tst_QSyntaxHighlighter::stopHighlightingWhenStateDoesNotChange()
{
    cursor.insertText("state");
    cursor.insertBlock();
    cursor.insertText("changestate");
    cursor.insertBlock();
    cursor.insertText("keepstate");
    cursor.insertBlock();
    cursor.insertText("changestate");
    cursor.insertBlock();
    cursor.insertText("changestate");

    StateTestHighlighter *hl = new StateTestHighlighter(doc);
    QTest::qWait(100);
    QVERIFY(hl->highlighted);

    hl->reset();

    // turn the text of the first block into 'changestate'
    cursor.movePosition(QTextCursor::Start);
    cursor.insertText("change");

    // verify that we highlighted only to the 'keepstate' block,
    // not beyond
    QCOMPARE(hl->state, 2);
}

void tst_QSyntaxHighlighter::unindent()
{
    const QString spaces("    ");
    const QString text("Foobar");
    QString plainText;
    for (int i = 0; i < 5; ++i) {
        cursor.insertText(spaces + text);
        cursor.insertBlock();

        plainText += spaces;
        plainText += text;
        plainText += QLatin1Char('\n');
    }
    QCOMPARE(doc->toPlainText(), plainText);

    TestHighlighter *hl = new TestHighlighter(doc);
    hl->callCount = 0;

    cursor.movePosition(QTextCursor::Start);
    cursor.beginEditBlock();

    plainText.clear();
    for (int i = 0; i < 5; ++i) {
        cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, 4);
        cursor.removeSelectedText();
        cursor.movePosition(QTextCursor::NextBlock);

        plainText += text;
        plainText += QLatin1Char('\n');
    }

    cursor.endEditBlock();
    QCOMPARE(doc->toPlainText(), plainText);
    QCOMPARE(hl->callCount, 5);
}

void tst_QSyntaxHighlighter::highlightToEndOfDocument()
{
    TestHighlighter *hl = new TestHighlighter(doc);
    hl->callCount = 0;

    cursor.movePosition(QTextCursor::Start);
    cursor.beginEditBlock();

    cursor.insertText("Hello");
    cursor.insertBlock();
    cursor.insertBlock();
    cursor.insertText("World");
    cursor.insertBlock();

    cursor.endEditBlock();

    QCOMPARE(hl->callCount, 4);
}

void tst_QSyntaxHighlighter::highlightToEndOfDocument2()
{
    TestHighlighter *hl = new TestHighlighter(doc);
    hl->callCount = 0;

    cursor.movePosition(QTextCursor::End);
    cursor.beginEditBlock();
    QTextBlockFormat fmt;
    fmt.setAlignment(Qt::AlignLeft);
    cursor.setBlockFormat(fmt);
    cursor.insertText("Three\nLines\nHere");
    cursor.endEditBlock();

    QCOMPARE(hl->callCount, 3);
}

void tst_QSyntaxHighlighter::preservePreeditArea()
{
    QList<QTextLayout::FormatRange> formats;
    QTextLayout::FormatRange range;
    range.start = 0;
    range.length = 8;
    range.format.setForeground(Qt::blue);
    formats << range;
    range.start = 9;
    range.length = 1;
    range.format.setForeground(Qt::red);
    formats << range;
    TestHighlighter *hl = new TestHighlighter(formats, doc);

    doc->setPlainText("Hello World");
    cursor.movePosition(QTextCursor::Start);

    QTextLayout *layout = cursor.block().layout();

    layout->setPreeditArea(5, QString("foo"));
    range.start = 5;
    range.length = 3;
    range.format.setFontUnderline(true);
    formats.clear();
    formats << range;

    hl->callCount = 0;

    cursor.beginEditBlock();
    layout->setAdditionalFormats(formats);
    cursor.endEditBlock();

    QCOMPARE(hl->callCount, 1);

    formats = layout->additionalFormats();
    QCOMPARE(formats.count(), 3);

    range = formats.at(0);

    QCOMPARE(range.start, 5);
    QCOMPARE(range.length, 3);
    QVERIFY(range.format.fontUnderline());

    range = formats.at(1);
    QCOMPARE(range.start, 0);
    QCOMPARE(range.length, 8 + 3);

    range = formats.at(2);
    QCOMPARE(range.start, 9 + 3);
    QCOMPARE(range.length, 1);
}

void tst_QSyntaxHighlighter::task108530()
{
    TestHighlighter *hl = new TestHighlighter(doc);

    cursor.insertText("test");
    hl->callCount = 0;
    hl->highlightedText.clear();
    cursor.movePosition(QTextCursor::Start);
    cursor.insertBlock();

    QCOMPARE(hl->highlightedText, QString("test"));
    QCOMPARE(hl->callCount, 2);
}

void tst_QSyntaxHighlighter::avoidUnnecessaryRehighlight()
{
    TestHighlighter *hl = new TestHighlighter(doc);
    QVERIFY(!hl->highlighted);

    doc->setPlainText("Hello World");
    QVERIFY(hl->highlighted);

    hl->highlighted = false;
    QTest::qWait(100);
    QVERIFY(!hl->highlighted);
}

void tst_QSyntaxHighlighter::noContentsChangedDuringHighlight()
{
    QList<QTextLayout::FormatRange> formats;
    QTextLayout::FormatRange range;
    range.start = 0;
    range.length = 10;
    range.format.setForeground(Qt::blue);
    formats.append(range);

    TestHighlighter *hl = new TestHighlighter(formats, doc);

    lout->documentChangedCalled = false;
    QTextCursor cursor(doc);

    QSignalSpy contentsChangedSpy(doc, SIGNAL(contentsChanged()));
    cursor.insertText("Hello World");

    QCOMPARE(contentsChangedSpy.count(), 1);
    QVERIFY(hl->highlighted);
    QVERIFY(lout->documentChangedCalled);
}

void tst_QSyntaxHighlighter::rehighlight()
{
    TestHighlighter *hl = new TestHighlighter(doc);
    hl->callCount = 0;
    doc->setPlainText("Hello");
    hl->callCount = 0;
    hl->rehighlight();
    QCOMPARE(hl->callCount, 1);
}

void tst_QSyntaxHighlighter::rehighlightBlock()
{
    TestHighlighter *hl = new TestHighlighter(doc);

    cursor.movePosition(QTextCursor::Start);
    cursor.beginEditBlock();
    cursor.insertText("Hello");
    cursor.insertBlock();
    cursor.insertText("World");
    cursor.endEditBlock();

    hl->callCount = 0;
    hl->highlightedText.clear();
    QTextBlock block = doc->begin();
    hl->rehighlightBlock(block);

    QCOMPARE(hl->highlightedText, QString("Hello"));
    QCOMPARE(hl->callCount, 1);

    hl->callCount = 0;
    hl->highlightedText.clear();
    hl->rehighlightBlock(block.next());

    QCOMPARE(hl->highlightedText, QString("World"));
    QCOMPARE(hl->callCount, 1);
}

QTEST_MAIN(tst_QSyntaxHighlighter)
#include "tst_qsyntaxhighlighter.moc"
<|endoftext|>


<commit_before>#include <string.h>

#include <string>

#include "BufferedFileReader.h"
#include "BufferedFileWriter.h"
#include "mcut.h"

#define BUFFER_SIZE     1048576

/*
* Reverse memchr()
* Find the last occurrence of 'c' in the buffer 's' of size 'n'.
*/
void *memrchr(const void *s, int c, size_t n)
{
    const unsigned char *cp;

    if (n != 0) {
        cp = (unsigned char *)s + n;

        do {
            if (*(--cp) == (unsigned char)c) {
                return (void *)cp;
            }
        } while (--n != 0);
    }

    return (void *)0;
}

void parseContent(const char *content, size_t contentSize, const char *pattern, size_t &part1Size, size_t &part2Size)
{
    const char *patternPos = strstr(content, pattern);

    if (!patternPos) {
        const void *lfPos = memrchr(content, '\n', contentSize);

        if (!lfPos) {
            part1Size = contentSize;
        } else {
            part1Size = (const char *)lfPos - content + 1;
        }

        part2Size = 0;
    } else {
        const void *lfPos = memrchr(content, '\n', patternPos - content);

        if (!lfPos) {
            part1Size = 0;
        } else {
            part1Size = (const char *)lfPos - content + 1;
        }

        part2Size = contentSize - part1Size;
    }
}

void process(const char *file, const char *pattern)
{
    std::string dest1Name = std::string(file) + "_1";
    std::string dest2Name = std::string(file) + "_2";

    BufferedFileReader reader(file);
    BufferedFileWriter writer1(dest1Name.c_str());
    BufferedFileWriter writer2(dest2Name.c_str());

    char *content = (char *)malloc(BUFFER_SIZE + 1);
    size_t contentSize = 0;

    size_t part1Size = 0;
    size_t part2Size = 0;

    // search and write part1/part2 data
    while (true) {
        contentSize += reader.read(content + contentSize, BUFFER_SIZE - contentSize);

        if (contentSize == 0) {
            break;
        }

        content[contentSize] = 0;

        parseContent(content, contentSize, pattern, part1Size, part2Size);

        // printf("part1Size = %ld, part2Size = %ld\n", part1Size, part2Size);
        putchar((part2Size > 0) ? '|' : '.');

        writer1.write(content, part1Size);
        writer2.write(content + part1Size, part2Size);

        if (part2Size > 0) {
            break;
        }

        contentSize -= part1Size;
        memmove(content, content + part1Size, contentSize);
    }

    free(content);

    // write part2 data
    const void *data = NULL;
    size_t dataSize = 0;

    while ((dataSize = reader.read(data)) > 0) {
        // printf("dataSize = %ld\n", 0, dataSize);
        putchar('.');

        writer2.write(data, dataSize);
    }

    putchar('\n');
}

void printHelp()
{
    printf("mcut: Cut a file based on match\n");
    printf("Usage\n");
    printf("$ mcut <pattern> <input_file>\n");
}

int main(int argc, char *argv[])
{
    if (argc != 3) {
        printHelp();
        return 1;
    }

    const char *pattern = argv[1];
    const char *file = argv[2];

    printf("pattern = %s\n", pattern);
    printf("file    = %s\n", file);
    printf("* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\n");

    process(file, pattern);

    return 0;
}
<commit_msg>fflush(stdout) to show progress<commit_after>#include <string.h>

#include <string>

#include "BufferedFileReader.h"
#include "BufferedFileWriter.h"
#include "mcut.h"

#define BUFFER_SIZE     1048576

/*
* Reverse memchr()
* Find the last occurrence of 'c' in the buffer 's' of size 'n'.
*/
void *memrchr(const void *s, int c, size_t n)
{
    const unsigned char *cp;

    if (n != 0) {
        cp = (unsigned char *)s + n;

        do {
            if (*(--cp) == (unsigned char)c) {
                return (void *)cp;
            }
        } while (--n != 0);
    }

    return (void *)0;
}

void parseContent(const char *content, size_t contentSize, const char *pattern, size_t &part1Size, size_t &part2Size)
{
    const char *patternPos = strstr(content, pattern);

    if (!patternPos) {
        const void *lfPos = memrchr(content, '\n', contentSize);

        if (!lfPos) {
            part1Size = contentSize;
        } else {
            part1Size = (const char *)lfPos - content + 1;
        }

        part2Size = 0;
    } else {
        const void *lfPos = memrchr(content, '\n', patternPos - content);

        if (!lfPos) {
            part1Size = 0;
        } else {
            part1Size = (const char *)lfPos - content + 1;
        }

        part2Size = contentSize - part1Size;
    }
}

void process(const char *file, const char *pattern)
{
    std::string dest1Name = std::string(file) + "_1";
    std::string dest2Name = std::string(file) + "_2";

    BufferedFileReader reader(file);
    BufferedFileWriter writer1(dest1Name.c_str());
    BufferedFileWriter writer2(dest2Name.c_str());

    char *content = (char *)malloc(BUFFER_SIZE + 1);
    size_t contentSize = 0;

    size_t part1Size = 0;
    size_t part2Size = 0;

    // search and write part1/part2 data
    while (true) {
        contentSize += reader.read(content + contentSize, BUFFER_SIZE - contentSize);

        if (contentSize == 0) {
            break;
        }

        content[contentSize] = 0;

        parseContent(content, contentSize, pattern, part1Size, part2Size);

        // printf("part1Size = %ld, part2Size = %ld\n", part1Size, part2Size);
        putchar((part2Size > 0) ? '|' : '.');
        fflush(stdout);

        writer1.write(content, part1Size);
        writer2.write(content + part1Size, part2Size);

        if (part2Size > 0) {
            break;
        }

        contentSize -= part1Size;
        memmove(content, content + part1Size, contentSize);
    }

    free(content);

    // write part2 data
    const void *data = NULL;
    size_t dataSize = 0;

    while ((dataSize = reader.read(data)) > 0) {
        // printf("dataSize = %ld\n", 0, dataSize);
        putchar('.');
        fflush(stdout);

        writer2.write(data, dataSize);
    }

    putchar('\n');
}

void printHelp()
{
    printf("mcut: Cut a file based on match\n");
    printf("Usage\n");
    printf("$ mcut <pattern> <input_file>\n");
}

int main(int argc, char *argv[])
{
    if (argc != 3) {
        printHelp();
        return 1;
    }

    const char *pattern = argv[1];
    const char *file = argv[2];

    printf("pattern = %s\n", pattern);
    printf("file    = %s\n", file);
    printf("* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\n");

    process(file, pattern);

    return 0;
}
<|endoftext|>


<commit_before>#include <nan.h>

#include "input.h"
#include "output.h"

Nan::Persistent<v8::FunctionTemplate> NodeMidiInput::s_ct;
Nan::Persistent<v8::FunctionTemplate> NodeMidiOutput::s_ct;

extern "C" {
    void init (v8::Local<v8::Object> target)
    {
        NodeMidiOutput::Init(target);
        NodeMidiInput::Init(target);
    }
    NODE_MODULE(midi, init)
}
<commit_msg>Use the NAN module init.<commit_after>#include <nan.h>

#include "input.h"
#include "output.h"

Nan::Persistent<v8::FunctionTemplate> NodeMidiInput::s_ct;
Nan::Persistent<v8::FunctionTemplate> NodeMidiOutput::s_ct;

NAN_MODULE_INIT(InitAll) {
    NodeMidiOutput::Init(target);
    NodeMidiInput::Init(target);
}

NODE_MODULE(midi, InitAll)
<|endoftext|>


<commit_before>
// This file is part of node-lmdb, the Node.js binding for lmdb
// Copyright (c) 2013 Timur Kristóf
// Licensed to you under the terms of the MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

#include "node-lmdb.h"
#include <string.h>
#include <stdio.h>

void setupExportMisc(Handle<Object> exports) {
    Local<Object> versionObj = Nan::New<Object>();

    int major, minor, patch;
    char *str = mdb_version(&major, &minor, &patch);
    versionObj->Set(Nan::New<String>("versionString").ToLocalChecked(), Nan::New<String>(str).ToLocalChecked());
    versionObj->Set(Nan::New<String>("major").ToLocalChecked(), Nan::New<Integer>(major));
    versionObj->Set(Nan::New<String>("minor").ToLocalChecked(), Nan::New<Integer>(minor));
    versionObj->Set(Nan::New<String>("patch").ToLocalChecked(), Nan::New<Integer>(patch));

    exports->Set(Nan::New<String>("version").ToLocalChecked(), versionObj);
}

void setFlagFromValue(int *flags, int flag, const char *name, bool defaultValue, Local<Object> options) {
    Handle<Value> opt = options->Get(Nan::New<String>(name).ToLocalChecked());
    if (opt->IsBoolean() ? opt->BooleanValue() : defaultValue) {
        *flags |= flag;
    }
}

argtokey_callback_t argToKey(const Handle<Value> &val, MDB_val &key, bool keyIsUint32) {
    // Check key type
    if (keyIsUint32 && !val->IsUint32()) {
        Nan::ThrowError("Invalid key. keyIsUint32 specified on the database, but the given key was not an unsigned 32-bit integer");
        return nullptr;
    }
    if (!keyIsUint32 && !val->IsString()) {
        Nan::ThrowError("Invalid key. String key expected, because keyIsUint32 isn't specified on the database.");
        return nullptr;
    }

    // Handle uint32_t key
    if (keyIsUint32) {
        uint32_t *v = new uint32_t;
        *v = val->Uint32Value();

        key.mv_size = sizeof(uint32_t);
        key.mv_data = v;

        return ([](MDB_val &key) -> void {
            delete (uint32_t*)key.mv_data;
        });
    }

    // Handle string key
    CustomExternalStringResource::writeTo(val->ToString(), &key);
    return ([](MDB_val &key) -> void {
        delete[] (uint16_t*)key.mv_data;
    });

    return nullptr;
}

Handle<Value> keyToHandle(MDB_val &key, bool keyIsUint32) {
    if (keyIsUint32) {
        return Nan::New<Integer>(*((uint32_t*)key.mv_data));
    }
    else {
        return valToString(key);
    }
}

Handle<Value> valToString(MDB_val &data) {
    auto resource = new CustomExternalStringResource(&data);
    auto str = String::NewExternalTwoByte(Isolate::GetCurrent(), resource);

    return str.ToLocalChecked();
    // return Nan::New<String>(str).ToLocalChecked();
}

Handle<Value> valToBinary(MDB_val &data) {
    // FIXME: It'd be better not to copy buffers, but I'm not sure
    // about the ownership semantics of MDB_val, so let' be safe.
    return Nan::CopyBuffer(
        (char*)data.mv_data,
        data.mv_size
    ).ToLocalChecked();
}

Handle<Value> valToNumber(MDB_val &data) {
    return Nan::New<Number>(*((double*)data.mv_data));
}

Handle<Value> valToBoolean(MDB_val &data) {
    return Nan::New<Boolean>(*((bool*)data.mv_data));
}

void consoleLog(const char *msg) {
    Local<String> str = Nan::New("console.log('").ToLocalChecked();
    str = String::Concat(str, Nan::New<String>(msg).ToLocalChecked());
    str = String::Concat(str, Nan::New("');").ToLocalChecked());

    Local<Script> script = Nan::CompileScript(str).ToLocalChecked();
    Nan::RunScript(script);
}

void consoleLog(Handle<Value> val) {
    Local<String> str = Nan::New<String>("console.log('").ToLocalChecked();
    str = String::Concat(str, val->ToString());
    str = String::Concat(str, Nan::New<String>("');").ToLocalChecked());

    Local<Script> script = Nan::CompileScript(str).ToLocalChecked();
    Nan::RunScript(script);
}

void consoleLogN(int n) {
    char c[20];
    memset(c, 0, 20 * sizeof(char));
    sprintf(c, "%d", n);
    consoleLog(c);
}

void CustomExternalStringResource::writeTo(Handle<String> str, MDB_val *val) {
    unsigned int l = str->Length() + 1;
    uint16_t *d = new uint16_t[l];
    str->Write(d);
    d[l - 1] = 0;

    val->mv_data = d;
    val->mv_size = l * sizeof(uint16_t);
}

CustomExternalStringResource::CustomExternalStringResource(MDB_val *val) {
    // The UTF-16 data
    this->d = (uint16_t*)(val->mv_data);
    // Number of UTF-16 characters in the string
    this->l = (val->mv_size / sizeof(uint16_t) - 1);
}

CustomExternalStringResource::~CustomExternalStringResource() { }

void CustomExternalStringResource::Dispose() {
    // No need to do anything, the data is owned by LMDB, not us
}

const uint16_t *CustomExternalStringResource::data() const {
    return this->d;
}

size_t CustomExternalStringResource::length() const {
    return this->l;
}
<commit_msg>removed dead code<commit_after>
// This file is part of node-lmdb, the Node.js binding for lmdb
// Copyright (c) 2013 Timur Kristóf
// Licensed to you under the terms of the MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

#include "node-lmdb.h"
#include <string.h>
#include <stdio.h>

void setupExportMisc(Handle<Object> exports) {
    Local<Object> versionObj = Nan::New<Object>();

    int major, minor, patch;
    char *str = mdb_version(&major, &minor, &patch);
    versionObj->Set(Nan::New<String>("versionString").ToLocalChecked(), Nan::New<String>(str).ToLocalChecked());
    versionObj->Set(Nan::New<String>("major").ToLocalChecked(), Nan::New<Integer>(major));
    versionObj->Set(Nan::New<String>("minor").ToLocalChecked(), Nan::New<Integer>(minor));
    versionObj->Set(Nan::New<String>("patch").ToLocalChecked(), Nan::New<Integer>(patch));

    exports->Set(Nan::New<String>("version").ToLocalChecked(), versionObj);
}

void setFlagFromValue(int *flags, int flag, const char *name, bool defaultValue, Local<Object> options) {
    Handle<Value> opt = options->Get(Nan::New<String>(name).ToLocalChecked());
    if (opt->IsBoolean() ? opt->BooleanValue() : defaultValue) {
        *flags |= flag;
    }
}

argtokey_callback_t argToKey(const Handle<Value> &val, MDB_val &key, bool keyIsUint32) {
    // Check key type
    if (keyIsUint32 && !val->IsUint32()) {
        Nan::ThrowError("Invalid key. keyIsUint32 specified on the database, but the given key was not an unsigned 32-bit integer");
        return nullptr;
    }
    if (!keyIsUint32 && !val->IsString()) {
        Nan::ThrowError("Invalid key. String key expected, because keyIsUint32 isn't specified on the database.");
        return nullptr;
    }

    // Handle uint32_t key
    if (keyIsUint32) {
        uint32_t *v = new uint32_t;
        *v = val->Uint32Value();

        key.mv_size = sizeof(uint32_t);
        key.mv_data = v;

        return ([](MDB_val &key) -> void {
            delete (uint32_t*)key.mv_data;
        });
    }

    // Handle string key
    CustomExternalStringResource::writeTo(val->ToString(), &key);
    return ([](MDB_val &key) -> void {
        delete[] (uint16_t*)key.mv_data;
    });

    return nullptr;
}

Handle<Value> keyToHandle(MDB_val &key, bool keyIsUint32) {
    if (keyIsUint32) {
        return Nan::New<Integer>(*((uint32_t*)key.mv_data));
    }
    else {
        return valToString(key);
    }
}

Handle<Value> valToString(MDB_val &data) {
    auto resource = new CustomExternalStringResource(&data);
    auto str = String::NewExternalTwoByte(Isolate::GetCurrent(), resource);

    return str.ToLocalChecked();
}

Handle<Value> valToBinary(MDB_val &data) {
    // FIXME: It'd be better not to copy buffers, but I'm not sure
    // about the ownership semantics of MDB_val, so let' be safe.
    return Nan::CopyBuffer(
        (char*)data.mv_data,
        data.mv_size
    ).ToLocalChecked();
}

Handle<Value> valToNumber(MDB_val &data) {
    return Nan::New<Number>(*((double*)data.mv_data));
}

Handle<Value> valToBoolean(MDB_val &data) {
    return Nan::New<Boolean>(*((bool*)data.mv_data));
}

void consoleLog(const char *msg) {
    Local<String> str = Nan::New("console.log('").ToLocalChecked();
    str = String::Concat(str, Nan::New<String>(msg).ToLocalChecked());
    str = String::Concat(str, Nan::New("');").ToLocalChecked());

    Local<Script> script = Nan::CompileScript(str).ToLocalChecked();
    Nan::RunScript(script);
}

void consoleLog(Handle<Value> val) {
    Local<String> str = Nan::New<String>("console.log('").ToLocalChecked();
    str = String::Concat(str, val->ToString());
    str = String::Concat(str, Nan::New<String>("');").ToLocalChecked());

    Local<Script> script = Nan::CompileScript(str).ToLocalChecked();
    Nan::RunScript(script);
}

void consoleLogN(int n) {
    char c[20];
    memset(c, 0, 20 * sizeof(char));
    sprintf(c, "%d", n);
    consoleLog(c);
}

void CustomExternalStringResource::writeTo(Handle<String> str, MDB_val *val) {
    unsigned int l = str->Length() + 1;
    uint16_t *d = new uint16_t[l];
    str->Write(d);
    d[l - 1] = 0;

    val->mv_data = d;
    val->mv_size = l * sizeof(uint16_t);
}

CustomExternalStringResource::CustomExternalStringResource(MDB_val *val) {
    // The UTF-16 data
    this->d = (uint16_t*)(val->mv_data);
    // Number of UTF-16 characters in the string
    this->l = (val->mv_size / sizeof(uint16_t) - 1);
}

CustomExternalStringResource::~CustomExternalStringResource() { }

void CustomExternalStringResource::Dispose() {
    // No need to do anything, the data is owned by LMDB, not us
}

const uint16_t *CustomExternalStringResource::data() const {
    return this->d;
}

size_t CustomExternalStringResource::length() const {
    return this->l;
}
<|endoftext|>


<commit_before>/*!
@file

@copyright Edouard Alligand and Joel Falcou 2015-2017
(See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
#ifndef BOOST_BRIGAND_FUNCTIONS_EVAL_IF_HPP
#define BOOST_BRIGAND_FUNCTIONS_EVAL_IF_HPP

#include <type_traits>

namespace brigand
{

  template <typename Condition, typename A, typename B>
  struct eval_if
  {
    using type = typename std::conditional<Condition::value, A, B>::type::type;
  };

  template <bool Condition, typename A, typename B>
  struct eval_if_c
  {
    using type = typename std::conditional<Condition, A, B>::type::type;
  };

}
#endif
<commit_msg>Delete eval_if.hpp<commit_after><|endoftext|>


<commit_before>/*
 *  Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#include <stdio.h>

#include <string>

#include "after_initialization_fixture.h"
#include "test/testsupport/fileutils.h"

namespace webrtc {
namespace {

const int16_t kLimiterHeadroom = 29204;  // == -1 dbFS
const int16_t kInt16Max = 0x7fff;
const int kSampleRateHz = 16000;
const int kTestDurationMs = 4000;

}  // namespace

class MixingTest : public AfterInitializationFixture {
 protected:
  MixingTest()
    : input_filename_(test::OutputPath() + "mixing_test_input.pcm"),
      output_filename_(test::OutputPath() + "mixing_test_output.pcm") {
  }

  // Creates and mixes |num_remote_streams| which play a file "as microphone"
  // with |num_local_streams| which play a file "locally", using a constant
  // amplitude of |input_value|. The local streams manifest as "anonymous"
  // mixing participants, meaning they will be mixed regardless of the number
  // of participants. (A stream is a VoiceEngine "channel").
  //
  // The mixed output is verified to always fall between |max_output_value| and
  // |min_output_value|, after a startup phase.
  //
  // |num_remote_streams_using_mono| of the remote streams use mono, with the
  // remainder using stereo.
  void RunMixingTest(int num_remote_streams,
                     int num_local_streams,
                     int num_remote_streams_using_mono,
                     int16_t input_value,
                     int16_t max_output_value,
                     int16_t min_output_value) {
    ASSERT_LE(num_remote_streams_using_mono, num_remote_streams);

    GenerateInputFile(input_value);

    std::vector<int> local_streams(num_local_streams);
    for (size_t i = 0; i < local_streams.size(); ++i) {
      local_streams[i] = voe_base_->CreateChannel();
      EXPECT_NE(-1, local_streams[i]);
    }
    StartLocalStreams(local_streams);
    TEST_LOG("Playing %d local streams.\n", num_local_streams);

    std::vector<int> remote_streams(num_remote_streams);
    for (size_t i = 0; i < remote_streams.size(); ++i) {
      remote_streams[i] = voe_base_->CreateChannel();
      EXPECT_NE(-1, remote_streams[i]);
    }
    StartRemoteStreams(remote_streams, num_remote_streams_using_mono);
    TEST_LOG("Playing %d remote streams.\n", num_remote_streams);

    // Start recording the mixed output and wait.
    EXPECT_EQ(0, voe_file_->StartRecordingPlayout(-1 /* record meeting */,
        output_filename_.c_str()));
    Sleep(kTestDurationMs);
    EXPECT_EQ(0, voe_file_->StopRecordingPlayout(-1));

    StopLocalStreams(local_streams);
    StopRemoteStreams(remote_streams);

    VerifyMixedOutput(max_output_value, min_output_value);

    // Cleanup the files in case another test uses different lengths.
    ASSERT_EQ(0, remove(input_filename_.c_str()));
    ASSERT_EQ(0, remove(output_filename_.c_str()));
  }

 private:
  // Generate input file with constant values equal to |input_value|. The file
  // will be one second longer than the duration of the test.
  void GenerateInputFile(int16_t input_value) {
    FILE* input_file = fopen(input_filename_.c_str(), "wb");
    ASSERT_TRUE(input_file != NULL);
    for (int i = 0; i < kSampleRateHz / 1000 * (kTestDurationMs + 1000); i++) {
      ASSERT_EQ(1u, fwrite(&input_value, sizeof(input_value), 1, input_file));
    }
    ASSERT_EQ(0, fclose(input_file));
  }

  void VerifyMixedOutput(int16_t max_output_value, int16_t min_output_value) {
    // Verify the mixed output.
    FILE* output_file = fopen(output_filename_.c_str(), "rb");
    ASSERT_TRUE(output_file != NULL);
    int16_t output_value = 0;
    // Skip the first 100 ms to avoid initialization and ramping-in effects.
    EXPECT_EQ(0, fseek(output_file, sizeof(output_value) * kSampleRateHz / 10,
                       SEEK_SET));
    int samples_read = 0;
    while (fread(&output_value, sizeof(output_value), 1, output_file) == 1) {
      samples_read++;
      EXPECT_LE(output_value, max_output_value);
      EXPECT_GE(output_value, min_output_value);
    }
    // Ensure the recording length is close to the duration of the test.
    ASSERT_GE((samples_read * 1000.0f) / kSampleRateHz,
              0.9f * kTestDurationMs);
    // Ensure we read the entire file.
    ASSERT_NE(0, feof(output_file));
    ASSERT_EQ(0, fclose(output_file));
  }

  // Start up local streams ("anonymous" participants).
  void StartLocalStreams(const std::vector<int>& streams) {
    for (size_t i = 0; i < streams.size(); ++i) {
      EXPECT_EQ(0, voe_base_->StartPlayout(streams[i]));
      EXPECT_EQ(0, voe_file_->StartPlayingFileLocally(streams[i],
          input_filename_.c_str(), true));
    }
  }

  void StopLocalStreams(const std::vector<int>& streams) {
    for (size_t i = 0; i < streams.size(); ++i) {
      EXPECT_EQ(0, voe_base_->StopPlayout(streams[i]));
      EXPECT_EQ(0, voe_base_->DeleteChannel(streams[i]));
    }
  }

  // Start up remote streams ("normal" participants).
  void StartRemoteStreams(const std::vector<int>& streams,
                          int num_remote_streams_using_mono) {
    // Use L16 at 16kHz to minimize distortion (file recording is 16kHz and
    // resampling will cause distortion).
    CodecInst codec_inst;
    strcpy(codec_inst.plname, "L16");
    codec_inst.channels = 1;
    codec_inst.plfreq = kSampleRateHz;
    codec_inst.pltype = 105;
    codec_inst.pacsize = codec_inst.plfreq / 100;
    codec_inst.rate = codec_inst.plfreq * sizeof(int16_t) * 8;  // 8 bits/byte.

    for (int i = 0; i < num_remote_streams_using_mono; ++i) {
      StartRemoteStream(streams[i], codec_inst, 1234 + 2 * i);
    }

    // The remainder of the streams will use stereo.
    codec_inst.channels = 2;
    codec_inst.pltype++;
    for (size_t i = num_remote_streams_using_mono; i < streams.size(); ++i) {
      StartRemoteStream(streams[i], codec_inst, 1234 + 2 * i);
    }
  }

  // Start up a single remote stream.
  void StartRemoteStream(int stream, const CodecInst& codec_inst, int port) {
    EXPECT_EQ(0, voe_codec_->SetRecPayloadType(stream, codec_inst));
    EXPECT_EQ(0, voe_base_->SetLocalReceiver(stream, port));
    EXPECT_EQ(0, voe_base_->SetSendDestination(stream, port, "127.0.0.1"));
    EXPECT_EQ(0, voe_base_->StartReceive(stream));
    EXPECT_EQ(0, voe_base_->StartPlayout(stream));
    EXPECT_EQ(0, voe_codec_->SetSendCodec(stream, codec_inst));
    EXPECT_EQ(0, voe_base_->StartSend(stream));
    EXPECT_EQ(0, voe_file_->StartPlayingFileAsMicrophone(stream,
        input_filename_.c_str(), true));
  }

  void StopRemoteStreams(const std::vector<int>& streams) {
    for (size_t i = 0; i < streams.size(); ++i) {
      EXPECT_EQ(0, voe_base_->StopSend(streams[i]));
      EXPECT_EQ(0, voe_base_->StopPlayout(streams[i]));
      EXPECT_EQ(0, voe_base_->StopReceive(streams[i]));
      EXPECT_EQ(0, voe_base_->DeleteChannel(streams[i]));
    }
  }

  const std::string input_filename_;
  const std::string output_filename_;
};

// These tests assume a maximum of three mixed participants. We typically allow
// a +/- 10% range around the expected output level to account for distortion
// from coding and processing in the loopback chain.
TEST_F(MixingTest, FourChannelsWithOnlyThreeMixed) {
  const int16_t kInputValue = 1000;
  const int16_t kExpectedOutput = kInputValue * 3;
  RunMixingTest(4, 0, 4, kInputValue, 1.1 * kExpectedOutput,
                0.9 * kExpectedOutput);
}

// Ensure the mixing saturation protection is working. We can do this because
// the mixing limiter is given some headroom, so the expected output is less
// than full scale.
TEST_F(MixingTest, VerifySaturationProtection) {
  const int16_t kInputValue = 20000;
  const int16_t kExpectedOutput = kLimiterHeadroom;
  // If this isn't satisfied, we're not testing anything.
  ASSERT_GT(kInputValue * 3, kInt16Max);
  ASSERT_LT(1.1 * kExpectedOutput, kInt16Max);
  RunMixingTest(3, 0, 3, kInputValue, 1.1 * kExpectedOutput,
               0.9 * kExpectedOutput);
}

TEST_F(MixingTest, SaturationProtectionHasNoEffectOnOneChannel) {
  const int16_t kInputValue = kInt16Max;
  const int16_t kExpectedOutput = kInt16Max;
  // If this isn't satisfied, we're not testing anything.
  ASSERT_GT(0.95 * kExpectedOutput, kLimiterHeadroom);
  // Tighter constraints are required here to properly test this.
  RunMixingTest(1, 0, 1, kInputValue, kExpectedOutput,
                0.95 * kExpectedOutput);
}

TEST_F(MixingTest, VerifyAnonymousAndNormalParticipantMixing) {
  const int16_t kInputValue = 1000;
  const int16_t kExpectedOutput = kInputValue * 2;
  RunMixingTest(1, 1, 1, kInputValue, 1.1 * kExpectedOutput,
                0.9 * kExpectedOutput);
}

TEST_F(MixingTest, AnonymousParticipantsAreAlwaysMixed) {
  const int16_t kInputValue = 1000;
  const int16_t kExpectedOutput = kInputValue * 4;
  RunMixingTest(3, 1, 3, kInputValue, 1.1 * kExpectedOutput,
                0.9 * kExpectedOutput);
}

TEST_F(MixingTest, VerifyStereoAndMonoMixing) {
  const int16_t kInputValue = 1000;
  const int16_t kExpectedOutput = kInputValue * 2;
  RunMixingTest(2, 0, 1, kInputValue, 1.1 * kExpectedOutput,
                0.9 * kExpectedOutput);
}

}  // namespace webrtc
<commit_msg>Avoid flakiness by skipping more output verification.<commit_after>/*
 *  Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#include <stdio.h>

#include <string>

#include "after_initialization_fixture.h"
#include "test/testsupport/fileutils.h"

namespace webrtc {
namespace {

const int16_t kLimiterHeadroom = 29204;  // == -1 dbFS
const int16_t kInt16Max = 0x7fff;
const int kSampleRateHz = 16000;
const int kTestDurationMs = 3000;
const int kSkipOutputMs = 500;

}  // namespace

class MixingTest : public AfterInitializationFixture {
 protected:
  MixingTest()
    : input_filename_(test::OutputPath() + "mixing_test_input.pcm"),
      output_filename_(test::OutputPath() + "mixing_test_output.pcm") {
  }

  // Creates and mixes |num_remote_streams| which play a file "as microphone"
  // with |num_local_streams| which play a file "locally", using a constant
  // amplitude of |input_value|. The local streams manifest as "anonymous"
  // mixing participants, meaning they will be mixed regardless of the number
  // of participants. (A stream is a VoiceEngine "channel").
  //
  // The mixed output is verified to always fall between |max_output_value| and
  // |min_output_value|, after a startup phase.
  //
  // |num_remote_streams_using_mono| of the remote streams use mono, with the
  // remainder using stereo.
  void RunMixingTest(int num_remote_streams,
                     int num_local_streams,
                     int num_remote_streams_using_mono,
                     int16_t input_value,
                     int16_t max_output_value,
                     int16_t min_output_value) {
    ASSERT_LE(num_remote_streams_using_mono, num_remote_streams);

    GenerateInputFile(input_value);

    std::vector<int> local_streams(num_local_streams);
    for (size_t i = 0; i < local_streams.size(); ++i) {
      local_streams[i] = voe_base_->CreateChannel();
      EXPECT_NE(-1, local_streams[i]);
    }
    StartLocalStreams(local_streams);
    TEST_LOG("Playing %d local streams.\n", num_local_streams);

    std::vector<int> remote_streams(num_remote_streams);
    for (size_t i = 0; i < remote_streams.size(); ++i) {
      remote_streams[i] = voe_base_->CreateChannel();
      EXPECT_NE(-1, remote_streams[i]);
    }
    StartRemoteStreams(remote_streams, num_remote_streams_using_mono);
    TEST_LOG("Playing %d remote streams.\n", num_remote_streams);

    // Start recording the mixed output and wait.
    EXPECT_EQ(0, voe_file_->StartRecordingPlayout(-1 /* record meeting */,
        output_filename_.c_str()));
    Sleep(kTestDurationMs);
    EXPECT_EQ(0, voe_file_->StopRecordingPlayout(-1));

    StopLocalStreams(local_streams);
    StopRemoteStreams(remote_streams);

    VerifyMixedOutput(max_output_value, min_output_value);

    // Cleanup the files in case another test uses different lengths.
    ASSERT_EQ(0, remove(input_filename_.c_str()));
    ASSERT_EQ(0, remove(output_filename_.c_str()));
  }

 private:
  // Generate input file with constant values equal to |input_value|. The file
  // will be one second longer than the duration of the test.
  void GenerateInputFile(int16_t input_value) {
    FILE* input_file = fopen(input_filename_.c_str(), "wb");
    ASSERT_TRUE(input_file != NULL);
    for (int i = 0; i < kSampleRateHz / 1000 * (kTestDurationMs + 1000); i++) {
      ASSERT_EQ(1u, fwrite(&input_value, sizeof(input_value), 1, input_file));
    }
    ASSERT_EQ(0, fclose(input_file));
  }

  void VerifyMixedOutput(int16_t max_output_value, int16_t min_output_value) {
    // Verify the mixed output.
    FILE* output_file = fopen(output_filename_.c_str(), "rb");
    ASSERT_TRUE(output_file != NULL);
    int16_t output_value = 0;
    // Skip the first segment to avoid initialization and ramping-in effects.
    EXPECT_EQ(0, fseek(output_file, sizeof(output_value) *
                       kSampleRateHz / 1000 * kSkipOutputMs, SEEK_SET));
    int samples_read = 0;
    while (fread(&output_value, sizeof(output_value), 1, output_file) == 1) {
      samples_read++;
      std::ostringstream trace_stream;
      trace_stream << samples_read << " samples read";
      SCOPED_TRACE(trace_stream.str());
      EXPECT_LE(output_value, max_output_value);
      EXPECT_GE(output_value, min_output_value);
    }
    // Ensure the recording length is close to the duration of the test.
    ASSERT_GE((samples_read * 1000.0f) / kSampleRateHz,
              kTestDurationMs - kSkipOutputMs);
    // Ensure we read the entire file.
    ASSERT_NE(0, feof(output_file));
    ASSERT_EQ(0, fclose(output_file));
  }

  // Start up local streams ("anonymous" participants).
  void StartLocalStreams(const std::vector<int>& streams) {
    for (size_t i = 0; i < streams.size(); ++i) {
      EXPECT_EQ(0, voe_base_->StartPlayout(streams[i]));
      EXPECT_EQ(0, voe_file_->StartPlayingFileLocally(streams[i],
          input_filename_.c_str(), true));
    }
  }

  void StopLocalStreams(const std::vector<int>& streams) {
    for (size_t i = 0; i < streams.size(); ++i) {
      EXPECT_EQ(0, voe_base_->StopPlayout(streams[i]));
      EXPECT_EQ(0, voe_base_->DeleteChannel(streams[i]));
    }
  }

  // Start up remote streams ("normal" participants).
  void StartRemoteStreams(const std::vector<int>& streams,
                          int num_remote_streams_using_mono) {
    // Use L16 at 16kHz to minimize distortion (file recording is 16kHz and
    // resampling will cause distortion).
    CodecInst codec_inst;
    strcpy(codec_inst.plname, "L16");
    codec_inst.channels = 1;
    codec_inst.plfreq = kSampleRateHz;
    codec_inst.pltype = 105;
    codec_inst.pacsize = codec_inst.plfreq / 100;
    codec_inst.rate = codec_inst.plfreq * sizeof(int16_t) * 8;  // 8 bits/byte.

    for (int i = 0; i < num_remote_streams_using_mono; ++i) {
      StartRemoteStream(streams[i], codec_inst, 1234 + 2 * i);
    }

    // The remainder of the streams will use stereo.
    codec_inst.channels = 2;
    codec_inst.pltype++;
    for (size_t i = num_remote_streams_using_mono; i < streams.size(); ++i) {
      StartRemoteStream(streams[i], codec_inst, 1234 + 2 * i);
    }
  }

  // Start up a single remote stream.
  void StartRemoteStream(int stream, const CodecInst& codec_inst, int port) {
    EXPECT_EQ(0, voe_codec_->SetRecPayloadType(stream, codec_inst));
    EXPECT_EQ(0, voe_base_->SetLocalReceiver(stream, port));
    EXPECT_EQ(0, voe_base_->SetSendDestination(stream, port, "127.0.0.1"));
    EXPECT_EQ(0, voe_base_->StartReceive(stream));
    EXPECT_EQ(0, voe_base_->StartPlayout(stream));
    EXPECT_EQ(0, voe_codec_->SetSendCodec(stream, codec_inst));
    EXPECT_EQ(0, voe_base_->StartSend(stream));
    EXPECT_EQ(0, voe_file_->StartPlayingFileAsMicrophone(stream,
        input_filename_.c_str(), true));
  }

  void StopRemoteStreams(const std::vector<int>& streams) {
    for (size_t i = 0; i < streams.size(); ++i) {
      EXPECT_EQ(0, voe_base_->StopSend(streams[i]));
      EXPECT_EQ(0, voe_base_->StopPlayout(streams[i]));
      EXPECT_EQ(0, voe_base_->StopReceive(streams[i]));
      EXPECT_EQ(0, voe_base_->DeleteChannel(streams[i]));
    }
  }

  const std::string input_filename_;
  const std::string output_filename_;
};

// These tests assume a maximum of three mixed participants. We typically allow
// a +/- 10% range around the expected output level to account for distortion
// from coding and processing in the loopback chain.
TEST_F(MixingTest, FourChannelsWithOnlyThreeMixed) {
  const int16_t kInputValue = 1000;
  const int16_t kExpectedOutput = kInputValue * 3;
  RunMixingTest(4, 0, 4, kInputValue, 1.1 * kExpectedOutput,
                0.9 * kExpectedOutput);
}

// Ensure the mixing saturation protection is working. We can do this because
// the mixing limiter is given some headroom, so the expected output is less
// than full scale.
TEST_F(MixingTest, VerifySaturationProtection) {
  const int16_t kInputValue = 20000;
  const int16_t kExpectedOutput = kLimiterHeadroom;
  // If this isn't satisfied, we're not testing anything.
  ASSERT_GT(kInputValue * 3, kInt16Max);
  ASSERT_LT(1.1 * kExpectedOutput, kInt16Max);
  RunMixingTest(3, 0, 3, kInputValue, 1.1 * kExpectedOutput,
               0.9 * kExpectedOutput);
}

TEST_F(MixingTest, SaturationProtectionHasNoEffectOnOneChannel) {
  const int16_t kInputValue = kInt16Max;
  const int16_t kExpectedOutput = kInt16Max;
  // If this isn't satisfied, we're not testing anything.
  ASSERT_GT(0.95 * kExpectedOutput, kLimiterHeadroom);
  // Tighter constraints are required here to properly test this.
  RunMixingTest(1, 0, 1, kInputValue, kExpectedOutput,
                0.95 * kExpectedOutput);
}

TEST_F(MixingTest, VerifyAnonymousAndNormalParticipantMixing) {
  const int16_t kInputValue = 1000;
  const int16_t kExpectedOutput = kInputValue * 2;
  RunMixingTest(1, 1, 1, kInputValue, 1.1 * kExpectedOutput,
                0.9 * kExpectedOutput);
}

TEST_F(MixingTest, AnonymousParticipantsAreAlwaysMixed) {
  const int16_t kInputValue = 1000;
  const int16_t kExpectedOutput = kInputValue * 4;
  RunMixingTest(3, 1, 3, kInputValue, 1.1 * kExpectedOutput,
                0.9 * kExpectedOutput);
}

TEST_F(MixingTest, VerifyStereoAndMonoMixing) {
  const int16_t kInputValue = 1000;
  const int16_t kExpectedOutput = kInputValue * 2;
  RunMixingTest(2, 0, 1, kInputValue, 1.1 * kExpectedOutput,
                0.9 * kExpectedOutput);
}

}  // namespace webrtc
<|endoftext|>