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:

committed by
Commit Bot

parent
9869e86fd9
commit
e4d0e92f4a
@ -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):
|
||||
|
549
tools/clang/scripts/apply_edits_test.py
Executable file
549
tools/clang/scripts/apply_edits_test.py
Executable file
@ -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()
|
Reference in New Issue
Block a user