0

Inserting an include of the new header during the rewrite.

Bug: 1069567
Change-Id: I85fa41b304db2f4902b0c2f46290f49279316f3d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2151338
Reviewed-by: Daniel Cheng <dcheng@chromium.org>
Reviewed-by: Bartek Nowierski <bartekn@chromium.org>
Commit-Queue: Łukasz Anforowicz <lukasza@chromium.org>
Cr-Commit-Position: refs/heads/master@{#766144}
This commit is contained in:
Lukasz Anforowicz
2020-05-06 20:47:12 +00:00
committed by Commit Bot
parent 9869e86fd9
commit e4d0e92f4a
10 changed files with 758 additions and 46 deletions

@ -54,8 +54,10 @@ Other useful references when writing the tool:
### Edit serialization format
```
==== BEGIN EDITS ====
r:::path/to/file1:::offset1:::length1:::replacement text
r:::path/to/file2:::offset2:::length2:::replacement text
r:::path/to/file/to/edit:::offset1:::length1:::replacement text
r:::path/to/file/to/edit:::offset2:::length2:::replacement text
r:::path/to/file2/to/edit:::offset3:::length3:::replacement text
include-user-header:::path/to/file2/to/edit:::-1:::-1:::header/file/to/include.h
...
@ -64,8 +66,8 @@ r:::path/to/file2:::offset2:::length2:::replacement text
The header and footer are required. Each line between the header and footer
represents one edit. Fields are separated by `:::`, and the first field must
be `r` (for replacement). In the future, this may be extended to handle header
insertion/removal. A deletion is an edit with no replacement text.
be `r` (for replacement) or `include-user-header`.
A deletion is an edit with no replacement text.
The edits are applied by [`apply_edits.py`](#Running), which understands certain
conventions:

@ -41,15 +41,48 @@
#include "llvm/Support/TargetSelect.h"
using namespace clang::ast_matchers;
using clang::tooling::CommonOptionsParser;
using clang::tooling::Replacement;
namespace {
// Include path that needs to be added to all the files where CheckedPtr<...>
// replaces a raw pointer.
const char kIncludePath[] = "base/memory/checked_ptr.h";
// Output format is documented in //docs/clang_tool_refactoring.md
class ReplacementsPrinter {
public:
ReplacementsPrinter() { llvm::outs() << "==== BEGIN EDITS ====\n"; }
~ReplacementsPrinter() { llvm::outs() << "==== END EDITS ====\n"; }
void PrintReplacement(const clang::SourceManager& source_manager,
const clang::SourceRange& replacement_range,
std::string replacement_text) {
clang::tooling::Replacement replacement(
source_manager, clang::CharSourceRange::getCharRange(replacement_range),
replacement_text);
llvm::StringRef file_path = replacement.getFilePath();
std::replace(replacement_text.begin(), replacement_text.end(), '\n', '\0');
llvm::outs() << "r:::" << file_path << ":::" << replacement.getOffset()
<< ":::" << replacement.getLength()
<< ":::" << replacement_text << "\n";
bool was_inserted = false;
std::tie(std::ignore, was_inserted) =
files_with_already_added_includes_.insert(file_path.str());
if (was_inserted)
llvm::outs() << "include-user-header:::" << file_path
<< ":::-1:::-1:::" << kIncludePath << "\n";
}
private:
std::set<std::string> files_with_already_added_includes_;
};
class FieldDeclRewriter : public MatchFinder::MatchCallback {
public:
explicit FieldDeclRewriter(std::vector<Replacement>* replacements)
: replacements_(replacements) {}
explicit FieldDeclRewriter(ReplacementsPrinter* replacements_printer)
: replacements_printer_(replacements_printer) {}
void run(const MatchFinder::MatchResult& result) override {
const clang::SourceManager& source_manager = *result.SourceManager;
@ -88,10 +121,9 @@ class FieldDeclRewriter : public MatchFinder::MatchCallback {
if (field_decl->isMutable())
replacement_text.insert(0, "mutable ");
// Generate and add a replacement.
replacements_->emplace_back(
source_manager, clang::CharSourceRange::getCharRange(replacement_range),
replacement_text);
// Generate and print a replacement.
replacements_printer_->PrintReplacement(source_manager, replacement_range,
replacement_text);
}
private:
@ -120,7 +152,7 @@ class FieldDeclRewriter : public MatchFinder::MatchCallback {
return result;
}
std::vector<Replacement>* const replacements_;
ReplacementsPrinter* const replacements_printer_;
};
} // namespace
@ -132,12 +164,12 @@ int main(int argc, const char* argv[]) {
llvm::InitializeNativeTargetAsmParser();
llvm::cl::OptionCategory category(
"rewrite_raw_ptr_fields: changes |T* field_| to |CheckedPtr<T> field_|.");
CommonOptionsParser options(argc, argv, category);
clang::tooling::CommonOptionsParser options(argc, argv, category);
clang::tooling::ClangTool tool(options.getCompilations(),
options.getSourcePathList());
MatchFinder match_finder;
std::vector<Replacement> replacements;
ReplacementsPrinter replacements_printer;
// Field declarations =========
// Given
@ -146,7 +178,7 @@ int main(int argc, const char* argv[]) {
// };
// matches |int* y|.
auto field_decl_matcher = fieldDecl(hasType(pointerType())).bind("fieldDecl");
FieldDeclRewriter field_decl_rewriter(&replacements);
FieldDeclRewriter field_decl_rewriter(&replacements_printer);
match_finder.addMatcher(field_decl_matcher, &field_decl_rewriter);
// Prepare and run the tool.
@ -156,15 +188,5 @@ int main(int argc, const char* argv[]) {
if (result != 0)
return result;
// Serialization format is documented in tools/clang/scripts/run_tool.py
llvm::outs() << "==== BEGIN EDITS ====\n";
for (const auto& r : replacements) {
std::string replacement_text = r.getReplacementText().str();
std::replace(replacement_text.begin(), replacement_text.end(), '\n', '\0');
llvm::outs() << "r:::" << r.getFilePath() << ":::" << r.getOffset()
<< ":::" << r.getLength() << ":::" << replacement_text << "\n";
}
llvm::outs() << "==== END EDITS ====\n";
return 0;
}

@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/memory/checked_ptr.h"
class SomeClass;
// Based on Chromium's //base/thread_annotations.h

@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/memory/checked_ptr.h"
class SomeClass;
class MyClass {

@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/memory/checked_ptr.h"
class SomeClass;
SomeClass* GetPointer();

@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/memory/checked_ptr.h"
class SomeClass;
class MyClass {

@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/memory/checked_ptr.h"
class SomeClass;
// Expected rewrite: typedef CheckedPtr<SomeClass> SomeClassPtrTypedef.

@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/memory/checked_ptr.h"
class SomeClass;
struct MyStruct {

@ -22,6 +22,7 @@ import functools
import multiprocessing
import os
import os.path
import re
import subprocess
import sys
@ -98,38 +99,164 @@ def _ParseEditsFromStdin(build_directory):
return edits
def _ApplyEditsToSingleFile(filename, edits):
_PLATFORM_SUFFIX = \
r'(?:_(?:android|aura|chromeos|ios|linux|mac|ozone|posix|win|x11))?'
_TEST_SUFFIX = \
r'(?:_(?:browser|interactive_ui|ui|unit)?test)?'
_suffix_regex = re.compile(_PLATFORM_SUFFIX + _TEST_SUFFIX)
def _FindPrimaryHeaderBasename(filepath):
""" Translates bar/foo.cc -> foo
bar/foo_posix.cc -> foo
bar/foo_unittest.cc -> foo
bar/foo.h -> None
"""
dirname, filename = os.path.split(filepath)
basename, extension = os.path.splitext(filename)
if extension == '.h':
return None
basename = _suffix_regex.sub('', basename)
return basename
_INCLUDE_INSERTION_POINT_REGEX_TEMPLATE = r'''
^(?! # Match the start of the first line that is
# not one of the following:
\s+ # 1. Line starting with whitespace
# (this includes blank lines and continuations of
# C comments that start with whitespace/indentation)
| // # 2a. A C++ comment
| /\* # 2b. A C comment
| \* # 2c. A continuation of a C comment (see also rule 1. above)
# 3. Include guards (Chromium-style)
| \#ifndef \s+ [A-Z0-9_]+_H ( | _ | __ ) \b \s* $
| \#define \s+ [A-Z0-9_]+_H ( | _ | __ ) \b \s* $
# 3b. Include guards (anything that repeats):
# - the same <guard> has to repeat in both the #ifndef and the #define
# - #define has to be "simple" - either:
# - either: #define GUARD
# - or : #define GUARD 1
| \#ifndef \s+ (?P<guard> [A-Za-z0-9_]* ) \s* $ ( \n | \r )* ^
\#define \s+ (?P=guard) \s* ( | 1 \s* ) $
| \#define \s+ (?P=guard) \s* ( | 1 \s* ) $ # Skipping previous line.
# 4. A C/C++ system include
| \#include \s* < .* >
# 5. A primary header include
# (%%s should be the basename returned by _FindPrimaryHeaderBasename).
#
# TODO(lukasza): Do not allow any directory below - require the top-level
# directory to be the same and at least one itermediate dirname to be the
# same.
| \#include \s* "
[^"]* \b # Allowing any directory
%s[^"/]*\.h " # Matching both basename.h and basename_posix.h
)
'''
def _InsertNonSystemIncludeHeader(filepath, header_line_to_add, contents):
""" Mutates |contents| (contents of |filepath|) to #include
the |header_to_add
"""
# Don't add the header if it is already present.
replacement_text = header_line_to_add
if replacement_text in contents:
return
replacement_text += '\n'
# Find the right insertion point.
#
# Note that we depend on a follow-up |git cl format| for the right order of
# headers. Therefore we just need to find the right header group (e.g. skip
# system headers and the primary header).
primary_header_basename = _FindPrimaryHeaderBasename(filepath)
if primary_header_basename is None:
primary_header_basename = ':this:should:never:match:'
regex_text = _INCLUDE_INSERTION_POINT_REGEX_TEMPLATE % primary_header_basename
match = re.search(regex_text, contents, re.MULTILINE | re.VERBOSE)
assert (match is not None)
insertion_point = match.start()
# Extra empty line is required if the addition is not adjacent to other
# includes.
if not contents[insertion_point:].startswith('#include'):
replacement_text += '\n'
# Make the edit.
contents[insertion_point:insertion_point] = replacement_text
def _ApplyReplacement(filepath, contents, edit, last_edit):
if (last_edit is not None and edit.edit_type == last_edit.edit_type
and edit.offset == last_edit.offset and edit.length == last_edit.length):
raise ValueError(('Conflicting replacement text: ' +
'%s at offset %d, length %d: "%s" != "%s"\n') %
(filepath, edit.offset, edit.length, edit.replacement,
last_edit.replacement))
contents[edit.offset:edit.offset + edit.length] = edit.replacement
if not edit.replacement:
_ExtendDeletionIfElementIsInList(contents, edit.offset)
def _ApplyIncludeHeader(filepath, contents, edit, last_edit):
header_line_to_add = '#include "%s"' % edit.replacement
_InsertNonSystemIncludeHeader(filepath, header_line_to_add, contents)
def _ApplySingleEdit(filepath, contents, edit, last_edit):
if edit.edit_type == 'r':
_ApplyReplacement(filepath, contents, edit, last_edit)
elif edit.edit_type == 'include-user-header':
_ApplyIncludeHeader(filepath, contents, edit, last_edit)
else:
raise ValueError('Unrecognized edit directive "%s": %s\n' %
(edit.edit_type, filepath))
def _ApplyEditsToSingleFileContents(filepath, contents, edits):
# Sort the edits and iterate through them in reverse order. Sorting allows
# duplicate edits to be quickly skipped, while reversing means that
# subsequent edits don't need to have their offsets updated with each edit
# applied.
#
# Note that after sorting in reverse, the 'i' directives will come after 'r'
# directives.
edits.sort(reverse=True)
edit_count = 0
error_count = 0
edits.sort()
last_edit = None
with open(filename, 'rb+') as f:
contents = bytearray(f.read())
for edit in reversed(edits):
if edit == last_edit:
continue
if (last_edit is not None and edit.edit_type == last_edit.edit_type and
edit.offset == last_edit.offset and edit.length == last_edit.length):
sys.stderr.write(
'Conflicting edit: %s at offset %d, length %d: "%s" != "%s"\n' %
(filename, edit.offset, edit.length, edit.replacement,
last_edit.replacement))
error_count += 1
continue
for edit in edits:
if edit == last_edit:
continue
try:
_ApplySingleEdit(filepath, contents, edit, last_edit)
last_edit = edit
contents[edit.offset:edit.offset + edit.length] = edit.replacement
if not edit.replacement:
_ExtendDeletionIfElementIsInList(contents, edit.offset)
edit_count += 1
except ValueError as err:
sys.stderr.write(str(err) + '\n')
error_count += 1
return (edit_count, error_count)
def _ApplyEditsToSingleFile(filepath, edits):
with open(filepath, 'rb+') as f:
contents = bytearray(f.read())
edit_and_error_counts = _ApplyEditsToSingleFileContents(
filepath, contents, edits)
f.seek(0)
f.truncate()
f.write(contents)
return (edit_count, error_count)
return edit_and_error_counts
def _ApplyEdits(edits):

@ -0,0 +1,549 @@
#!/usr/bin/env python
# Copyright 2020 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.
import unittest
import apply_edits
def _FindPHB(filepath):
return apply_edits._FindPrimaryHeaderBasename(filepath)
class FindPrimaryHeaderBasenameTest(unittest.TestCase):
def testNoOpOnHeader(self):
self.assertIsNone(_FindPHB('bar.h'))
self.assertIsNone(_FindPHB('foo/bar.h'))
def testStripDirectories(self):
self.assertEqual('bar', _FindPHB('foo/bar.cc'))
def testStripPlatformSuffix(self):
self.assertEqual('bar', _FindPHB('bar_posix.cc'))
self.assertEqual('bar', _FindPHB('bar_unittest.cc'))
def testStripTestSuffix(self):
self.assertEqual('bar', _FindPHB('bar_browsertest.cc'))
self.assertEqual('bar', _FindPHB('bar_unittest.cc'))
def testStripPlatformAndTestSuffix(self):
self.assertEqual('bar', _FindPHB('bar_uitest_aura.cc'))
self.assertEqual('bar', _FindPHB('bar_linux_unittest.cc'))
def testNoSuffixStrippingWithoutUnderscore(self):
self.assertEqual('barunittest', _FindPHB('barunittest.cc'))
def _ApplyEdit(old_contents_string,
edit,
contents_filepath="some_file.cc",
last_edit=None):
if last_edit is not None:
assert (last_edit > edit) # Test or prod caller should ensure.
ba = bytearray()
ba.extend(old_contents_string.encode('ASCII'))
apply_edits._ApplySingleEdit(contents_filepath, ba, edit, last_edit)
return ba.decode('ASCII')
def _InsertHeader(old_contents,
contents_filepath='foo/impl.cc',
new_header_path='new/header.h'):
edit = apply_edits.Edit('include-user-header', -1, -1, new_header_path)
return _ApplyEdit(old_contents, edit, contents_filepath=contents_filepath)
class InsertIncludeHeaderTest(unittest.TestCase):
def testSkippingCppComments(self):
old_contents = '''
// Copyright info here.
#include "old/header.h"
'''
expected_new_contents = '''
// Copyright info here.
#include "new/header.h"
#include "old/header.h"
'''
new_header_line = '#include "new/header.h'
self.assertEqual(expected_new_contents, _InsertHeader(old_contents))
def testSkippingOldStyleComments(self):
old_contents = '''
/* Copyright
* info here.
*/
#include "old/header.h"
'''
expected_new_contents = '''
/* Copyright
* info here.
*/
#include "new/header.h"
#include "old/header.h"
'''
self.assertEqual(expected_new_contents, _InsertHeader(old_contents))
def testSkippingOldStyleComments_NoWhitespaceAtLineStart(self):
old_contents = '''
/* Copyright
* info here.
*/
#include "old/header.h"
'''
expected_new_contents = '''
/* Copyright
* info here.
*/
#include "new/header.h"
#include "old/header.h"
'''
self.assertEqual(expected_new_contents, _InsertHeader(old_contents))
def testSkippingSystemHeaders(self):
old_contents = '''
#include <string>
#include <vector> // blah
#include "old/header.h"
'''
expected_new_contents = '''
#include <string>
#include <vector> // blah
#include "new/header.h"
#include "old/header.h"
'''
self.assertEqual(expected_new_contents, _InsertHeader(old_contents))
def testSkippingPrimaryHeader(self):
old_contents = '''
// Copyright info here.
#include "foo/impl.h"
#include "old/header.h"
'''
expected_new_contents = '''
// Copyright info here.
#include "foo/impl.h"
#include "new/header.h"
#include "old/header.h"
'''
self.assertEqual(expected_new_contents, _InsertHeader(old_contents))
def testSimilarNonPrimaryHeader_WithPrimaryHeader(self):
old_contents = '''
// Copyright info here.
#include "primary/impl.h" // This is the primary header.
#include "unrelated/impl.h" // This is *not* the primary header.
#include "zzz/foo.h"
'''
expected_new_contents = '''
// Copyright info here.
#include "primary/impl.h" // This is the primary header.
#include "unrelated/impl.h" // This is *not* the primary header.
#include "new/header.h"
#include "zzz/foo.h"
'''
self.assertEqual(expected_new_contents, _InsertHeader(old_contents))
def testSimilarNonPrimaryHeader_NoPrimaryHeader(self):
old_contents = '''
// Copyright info here.
#include "unrelated/impl.h" // This is *not* the primary header.
#include "zzz/foo.h"
'''
expected_new_contents = '''
// Copyright info here.
#include "unrelated/impl.h" // This is *not* the primary header.
#include "new/header.h"
#include "zzz/foo.h"
'''
self.assertEqual(expected_new_contents, _InsertHeader(old_contents))
def testSkippingIncludeGuards(self):
old_contents = '''
#ifndef FOO_IMPL_H_
#define FOO_IMPL_H_
#include "old/header.h"
#endif FOO_IMPL_H_
'''
expected_new_contents = '''
#ifndef FOO_IMPL_H_
#define FOO_IMPL_H_
#include "new/header.h"
#include "old/header.h"
#endif FOO_IMPL_H_
'''
self.assertEqual(expected_new_contents,
_InsertHeader(old_contents, 'foo/impl.h', 'new/header.h'))
def testSkippingIncludeGuards2(self):
# This test is based on base/third_party/valgrind/memcheck.h
old_contents = '''
#ifndef __MEMCHECK_H
#define __MEMCHECK_H
#include "old/header.h"
#endif
'''
expected_new_contents = '''
#ifndef __MEMCHECK_H
#define __MEMCHECK_H
#include "new/header.h"
#include "old/header.h"
#endif
'''
self.assertEqual(expected_new_contents, _InsertHeader(old_contents))
def testSkippingIncludeGuards3(self):
# This test is based on base/third_party/xdg_mime/xdgmime.h
old_contents = '''
#ifndef __XDG_MIME_H__
#define __XDG_MIME_H__
#include "old/header.h"
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
typedef void (*XdgMimeCallback) (void *user_data);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* __XDG_MIME_H__ */
'''
expected_new_contents = '''
#ifndef __XDG_MIME_H__
#define __XDG_MIME_H__
#include "new/header.h"
#include "old/header.h"
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
typedef void (*XdgMimeCallback) (void *user_data);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* __XDG_MIME_H__ */
'''
self.assertEqual(expected_new_contents, _InsertHeader(old_contents))
def testSkippingIncludeGuards4(self):
# This test is based on ash/first_run/desktop_cleaner.h and/or
# components/subresource_filter/core/common/scoped_timers.h and/or
# device/gamepad/abstract_haptic_gamepad.h
old_contents = '''
#ifndef ASH_FIRST_RUN_DESKTOP_CLEANER_
#define ASH_FIRST_RUN_DESKTOP_CLEANER_
#include "old/header.h"
namespace ash {
} // namespace ash
#endif // ASH_FIRST_RUN_DESKTOP_CLEANER_
'''
expected_new_contents = '''
#ifndef ASH_FIRST_RUN_DESKTOP_CLEANER_
#define ASH_FIRST_RUN_DESKTOP_CLEANER_
#include "new/header.h"
#include "old/header.h"
namespace ash {
} // namespace ash
#endif // ASH_FIRST_RUN_DESKTOP_CLEANER_
'''
self.assertEqual(expected_new_contents, _InsertHeader(old_contents))
def testSkippingIncludeGuards5(self):
# This test is based on third_party/weston/include/GLES2/gl2.h (the |extern
# "C"| part has been removed to make the test trickier to handle right -
# otherwise it is easy to see that the header has to be included before the
# |extern "C"| part).
#
# The tricky parts below include:
# 1. upper + lower case characters allowed in the guard name
# 2. Having to recognize that GL_APIENTRYP is *not* a guard
old_contents = '''
#ifndef __gles2_gl2_h_
#define __gles2_gl2_h_ 1
#include <GLES2/gl2platform.h>
#ifndef GL_APIENTRYP
#define GL_APIENTRYP GL_APIENTRY*
#endif
#endif
'''
expected_new_contents = '''
#ifndef __gles2_gl2_h_
#define __gles2_gl2_h_ 1
#include <GLES2/gl2platform.h>
#include "new/header.h"
#ifndef GL_APIENTRYP
#define GL_APIENTRYP GL_APIENTRY*
#endif
#endif
'''
self.assertEqual(expected_new_contents, _InsertHeader(old_contents))
def testSkippingIncludeGuards6(self):
# This test is based on ios/third_party/blink/src/html_token.h
old_contents = '''
#ifndef HTMLToken_h
#define HTMLToken_h
#include <stddef.h>
#include <vector>
#include "base/macros.h"
// ...
#endif
'''
expected_new_contents = '''
#ifndef HTMLToken_h
#define HTMLToken_h
#include <stddef.h>
#include <vector>
#include "new/header.h"
#include "base/macros.h"
// ...
#endif
'''
self.assertEqual(expected_new_contents, _InsertHeader(old_contents))
def testNoOpIfAlreadyPresent(self):
# This tests that the new header won't be inserted (and duplicated)
# if it is already included.
old_contents = '''
// Copyright info here.
#include "old/header.h"
#include "new/header.h"
#include "new/header2.h"
'''
expected_new_contents = '''
// Copyright info here.
#include "old/header.h"
#include "new/header.h"
#include "new/header2.h"
'''
self.assertEqual(expected_new_contents, _InsertHeader(old_contents))
def testNoOpIfAlreadyPresent_WithTrailingComment(self):
# This tests that the new header won't be inserted (and duplicated)
# if it is already included.
old_contents = '''
// Copyright info here.
#include "old/header.h"
#include "new/header.h" // blah
#include "new/header2.h"
'''
expected_new_contents = '''
// Copyright info here.
#include "old/header.h"
#include "new/header.h" // blah
#include "new/header2.h"
'''
self.assertEqual(expected_new_contents, _InsertHeader(old_contents))
def testNoOldHeaders(self):
# This tests that an extra new line is inserted after the new header
# when there are no old headers immediately below.
old_contents = '''
#include <vector>
struct S {};
'''
expected_new_contents = '''
#include <vector>
#include "new/header.h"
struct S {};
'''
self.assertEqual(expected_new_contents, _InsertHeader(old_contents))
def testPlatformIfDefs(self):
# This test is based on
# //base/third_party/double_conversion/double-conversion/utils.h
# We need to insert the new header in a non-conditional part.
old_contents = '''
#ifndef DOUBLE_CONVERSION_UTILS_H_
#define DOUBLE_CONVERSION_UTILS_H_
#include <cstdlib>
#include <cstring>
#ifndef DOUBLE_CONVERSION_UNREACHABLE
#ifdef _MSC_VER
void DOUBLE_CONVERSION_NO_RETURN abort_noreturn();
inline void abort_noreturn() { abort(); }
#define DOUBLE_CONVERSION_UNREACHABLE() (abort_noreturn())
#else
#define DOUBLE_CONVERSION_UNREACHABLE() (abort())
#endif
#endif
namespace double_conversion {
'''
expected_new_contents = '''
#ifndef DOUBLE_CONVERSION_UTILS_H_
#define DOUBLE_CONVERSION_UTILS_H_
#include <cstdlib>
#include <cstring>
#include "new/header.h"
#ifndef DOUBLE_CONVERSION_UNREACHABLE
#ifdef _MSC_VER
void DOUBLE_CONVERSION_NO_RETURN abort_noreturn();
inline void abort_noreturn() { abort(); }
#define DOUBLE_CONVERSION_UNREACHABLE() (abort_noreturn())
#else
#define DOUBLE_CONVERSION_UNREACHABLE() (abort())
#endif
#endif
namespace double_conversion {
'''
self.assertEqual(expected_new_contents, _InsertHeader(old_contents))
def testNoOldIncludesAndIfDefs(self):
# Artificial test: no old #includes + some #ifdefs. The main focus of the
# test is ensuring that the new header will be inserted into the
# unconditional part of the file.
old_contents = '''
#ifndef NDEBUG
#include "base/logging.h"
#endif
void foo();
'''
expected_new_contents = '''
#include "new/header.h"
#ifndef NDEBUG
#include "base/logging.h"
#endif
void foo();
'''
self.assertEqual(expected_new_contents, _InsertHeader(old_contents))
def testNoOldIncludesAndIfDefs2(self):
# Artificial test: no old #includes + some #ifdefs. The main focus of the
# test is ensuring that the new header will be inserted into the
# unconditional part of the file.
old_contents = '''
#if defined(OS_WIN)
#include "foo_win.h"
#endif
void foo();
'''
expected_new_contents = '''
#include "new/header.h"
#if defined(OS_WIN)
#include "foo_win.h"
#endif
void foo();
'''
self.assertEqual(expected_new_contents, _InsertHeader(old_contents))
def _CreateReplacement(content_string, old_substring, new_substring):
""" Test helper for creating an Edit object with the right offset, etc. """
offset = content_string.find(old_substring)
return apply_edits.Edit('r', offset, len(old_substring), new_substring)
class ApplyReplacementTest(unittest.TestCase):
def testBasics(self):
old_text = "123 456 789"
r = _CreateReplacement(old_text, "456", "foo")
new_text = _ApplyEdit(old_text, r)
self.assertEqual("123 foo 789", new_text)
def testMiddleListElementRemoval(self):
old_text = "(123, 456, 789) // foobar"
r = _CreateReplacement(old_text, "456", "")
new_text = _ApplyEdit(old_text, r)
self.assertEqual("(123, 789) // foobar", new_text)
def testFinalElementRemoval(self):
old_text = "(123, 456, 789) // foobar"
r = _CreateReplacement(old_text, "789", "")
new_text = _ApplyEdit(old_text, r)
self.assertEqual("(123, 456) // foobar", new_text)
def testConflictingReplacement(self):
old_text = "123 456 789"
last = _CreateReplacement(old_text, "456", "foo")
edit = _CreateReplacement(old_text, "456", "bar")
expected_msg_regex = 'Conflicting replacement text'
expected_msg_regex += '.*some_file.cc at offset 4, length 3'
expected_msg_regex += '.*"bar" != "foo"'
with self.assertRaisesRegexp(ValueError, expected_msg_regex):
_ApplyEdit(old_text, edit, last_edit=last)
def testUnrecognizedEditDirective(self):
old_text = "123 456 789"
edit = apply_edits.Edit('unknown_directive', 123, 456, "foo")
expected_msg_regex = 'Unrecognized edit directive "unknown_directive"'
expected_msg_regex += '.*some_file.cc'
with self.assertRaisesRegexp(ValueError, expected_msg_regex):
_ApplyEdit(old_text, edit)
if __name__ == '__main__':
unittest.main()