blob: 91da03da2b52f83f5563a03d8c1c746d411f2b6c [file] [log] [blame] [edit]
#!/usr/bin/env vpython3
# Copyright 2020 The Chromium Authors
# 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('utf-8'))
return apply_edits._ApplySingleEdit(contents_filepath,
old_contents_string.encode("utf-8"), edit,
last_edit).decode("utf-8")
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.encode("utf-8"))
return _ApplyEdit(old_contents, edit, contents_filepath)
class InsertIncludeHeaderTest(unittest.TestCase):
def _assertEqualContents(self, expected, actual):
if expected != actual:
print("####################### EXPECTED:")
print(expected)
print("####################### ACTUAL:")
print(actual)
print("####################### END.")
self.assertEqual(expected, actual)
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._assertEqualContents(expected_new_contents,
_InsertHeader(old_contents))
def testSkippingCppComments_DocCommentForStruct(self):
""" This is a regression test for https://crbug.com/1175684 """
old_contents = '''
// Copyright blah blah...
#ifndef SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_FILTER_H_
#define SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_FILTER_H_
#include <stdint.h>
// Doc comment for a struct.
// Multiline.
struct sock_filter {
uint16_t code;
};
'''
expected_new_contents = '''
// Copyright blah blah...
#ifndef SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_FILTER_H_
#define SANDBOX_LINUX_SYSTEM_HEADERS_LINUX_FILTER_H_
#include <stdint.h>
#include "new/header.h"
// Doc comment for a struct.
// Multiline.
struct sock_filter {
uint16_t code;
};
'''
new_header_line = '#include "new/header.h'
self._assertEqualContents(expected_new_contents,
_InsertHeader(old_contents))
def testSkippingCppComments_DocCommentForStruct2(self):
""" This is a regression test for https://crbug.com/1175684 """
old_contents = '''
// Copyright blah blah...
// Doc comment for a struct.
struct sock_filter {
uint16_t code;
};
'''
expected_new_contents = '''
// Copyright blah blah...
#include "new/header.h"
// Doc comment for a struct.
struct sock_filter {
uint16_t code;
};
'''
new_header_line = '#include "new/header.h'
self._assertEqualContents(expected_new_contents,
_InsertHeader(old_contents))
def testSkippingCppComments_DocCommentForStruct3(self):
""" This is a regression test for https://crbug.com/1175684 """
old_contents = '''
// Doc comment for a struct.
struct sock_filter {
uint16_t code;
};
'''
expected_new_contents = '''
#include "new/header.h"
// Doc comment for a struct.
struct sock_filter {
uint16_t code;
};
'''
new_header_line = '#include "new/header.h'
self._assertEqualContents(expected_new_contents,
_InsertHeader(old_contents))
def testSkippingCppComments_DocCommentForInclude(self):
""" This is a regression test for https://crbug.com/1175684 """
old_contents = '''
// Copyright blah blah...
// System includes.
#include <stdint.h>
// Doc comment for a struct.
struct sock_filter {
uint16_t code;
};
'''
expected_new_contents = '''
// Copyright blah blah...
// System includes.
#include <stdint.h>
#include "new/header.h"
// Doc comment for a struct.
struct sock_filter {
uint16_t code;
};
'''
new_header_line = '#include "new/header.h'
self._assertEqualContents(expected_new_contents,
_InsertHeader(old_contents))
def testSkippingCppComments_DocCommentForWholeFile(self):
""" This is a regression test for https://crbug.com/1175684 """
old_contents = '''
// Copyright blah blah...
// Doc comment for the whole file.
struct sock_filter {
uint16_t code;
};
'''
expected_new_contents = '''
// Copyright blah blah...
// Doc comment for the whole file.
#include "new/header.h"
struct sock_filter {
uint16_t code;
};
'''
new_header_line = '#include "new/header.h'
self._assertEqualContents(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._assertEqualContents(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._assertEqualContents(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._assertEqualContents(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._assertEqualContents(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._assertEqualContents(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._assertEqualContents(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._assertEqualContents(
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._assertEqualContents(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._assertEqualContents(expected_new_contents,
_InsertHeader(old_contents))
@unittest.skip(
"Failing test due to regex (in apply_edits.py) not working as expected, please fix."
)
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._assertEqualContents(expected_new_contents,
_InsertHeader(old_contents))
@unittest.skip(
"Failing test due to regex (in apply_edits.py) not working as expected, please fix."
)
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._assertEqualContents(expected_new_contents,
_InsertHeader(old_contents))
@unittest.skip(
"Failing test due to regex (in apply_edits.py) not working as expected, please fix."
)
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>
// ...
#endif
'''
expected_new_contents = '''
#ifndef HTMLToken_h
#define HTMLToken_h
#include <stddef.h>
#include <vector>
#include "new/header.h"
// ...
#endif
'''
self._assertEqualContents(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._assertEqualContents(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._assertEqualContents(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._assertEqualContents(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._assertEqualContents(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._assertEqualContents(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 BUILDFLAG(IS_WIN)
#include "foo_win.h"
#endif
void foo();
'''
expected_new_contents = '''
#include "new/header.h"
#if BUILDFLAG(IS_WIN)
#include "foo_win.h"
#endif
void foo();
'''
self._assertEqualContents(expected_new_contents,
_InsertHeader(old_contents))
def testUtf8BomMarker(self):
# Test based on
# //chrome/browser/ui/views/payments/payment_sheet_view_controller.cc
# which at some point began as follows:
# 00000000: efbb bf2f 2f20 436f 7079 7269 6768 7420 ...// Copyright
#
# Previous versions of apply_edits.py would not skip the BOM marker when
# figuring out where to insert the new include header.
old_contents = u'''\ufeff// Copyright
#include "old/header.h"
'''
expected_new_contents = u'''\ufeff// Copyright
#include "new/header.h"
#include "old/header.h"
'''
actual = bytearray()
actual.extend(old_contents.encode('utf-8'))
expected = bytearray()
expected.extend(expected_new_contents.encode('utf-8'))
# Test sanity check (i.e. not an assertion about code under test).
utf8_bom = [0xef, 0xbb, 0xbf]
self._assertEqualContents(list(actual[0:3]), utf8_bom)
self._assertEqualContents(list(expected[0:3]), utf8_bom)
# Actual test.
edit = apply_edits.Edit('include-user-header', -1, -1, b"new/header.h")
actual = apply_edits._ApplySingleEdit("foo/impl.cc", actual, edit, None)
self._assertEqualContents(expected, actual)
def _CreateReplacement(content_string, old_substring, new_substring):
""" Test helper for creating an Edit object with the right offset, etc. """
b_content_string = content_string.encode("utf-8")
b_old_string = old_substring.encode("utf-8")
b_new_string = new_substring.encode("utf-8")
offset = b_content_string.find(b_old_string)
return apply_edits.Edit('r', offset, len(b_old_string), b_new_string)
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.assertRaisesRegex(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.assertRaisesRegex(ValueError, expected_msg_regex):
_ApplyEdit(old_text, edit)
def testOverlappingReplacement(self):
old_text = "123 456 789"
last = _CreateReplacement(old_text, "456 789", "foo")
edit = _CreateReplacement(old_text, "123 456", "bar")
expected_msg_regex = 'Overlapping replacements'
expected_msg_regex += '.*some_file.cc'
expected_msg_regex += '.*offset 0, length 7.*"bar"'
expected_msg_regex += '.*offset 4, length 7.*"foo"'
with self.assertRaisesRegex(ValueError, expected_msg_regex):
_ApplyEdit(old_text, edit, last_edit=last)
if __name__ == '__main__':
unittest.main()