summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSylvestre Ledru <sylvestre@debian.org>2022-04-08 13:05:27 +0200
committerSylvestre Ledru <sylvestre@debian.org>2022-04-08 13:05:27 +0200
commit714666cfd0ba402bc9a07f53492eaccd11553ea3 (patch)
tree096945186137181df00959ab66b88c1e44f09c56
parentf11dff66fa5dde165b562407d1eebca59080f913 (diff)
parentd7d87779db4b83cf637f6d2362927ce9cbe17b15 (diff)
Update upstream source from tag 'upstream/8.18'
Update to upstream version '8.18' with Debian dir 995b682507fe4c3614ef935a3cdae72b40297d4b
-rw-r--r--.github/workflows/ci.yml9
-rw-r--r--README.md20
-rwxr-xr-xfix_includes.py13
-rw-r--r--include-what-you-use.142
-rw-r--r--iwyu.cc69
-rw-r--r--iwyu_ast_util.cc39
-rw-r--r--iwyu_ast_util.h4
-rw-r--r--iwyu_globals.cc52
-rw-r--r--iwyu_globals.h11
-rw-r--r--iwyu_preprocessor.cc4
-rwxr-xr-xiwyu_test_util.py30
-rwxr-xr-xiwyu_tool.py6
-rwxr-xr-xiwyu_tool_test.py33
-rw-r--r--iwyu_version.h2
-rwxr-xr-xrun_iwyu_tests.py3
-rw-r--r--tests/cxx/consteval.cc51
-rw-r--r--tests/driver/direct.h15
-rw-r--r--tests/driver/exitcode_bad_args.c16
-rw-r--r--tests/driver/exitcode_good.c16
-rw-r--r--tests/driver/exitcode_good_error.c19
-rw-r--r--tests/driver/exitcode_good_error_always.c19
-rw-r--r--tests/driver/exitcode_good_error_always_arg.c19
-rw-r--r--tests/driver/exitcode_good_error_and_always.c18
-rw-r--r--tests/driver/exitcode_good_error_arg.c19
-rw-r--r--tests/driver/exitcode_syntax_error.c19
-rw-r--r--tests/driver/exitcode_warn.c30
-rw-r--r--tests/driver/exitcode_warn_error.c31
-rw-r--r--tests/driver/exitcode_warn_error_always.c32
-rw-r--r--tests/driver/exitcode_warn_error_always_arg.c32
-rw-r--r--tests/driver/exitcode_warn_error_and_always.c30
-rw-r--r--tests/driver/exitcode_warn_error_arg.c31
-rw-r--r--tests/driver/indirect.h17
32 files changed, 664 insertions, 87 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index acecc08..1bdd7ee 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,8 +1,11 @@
name: IWYU CI
on:
- - push
- - pull_request
+ push:
+ pull_request:
+ schedule:
+ # Run build of master at 03:38 every day
+ - cron: '38 3 * * *'
defaults:
run:
@@ -15,7 +18,7 @@ jobs:
fail-fast: false
env:
- LLVM_TAG: -13
+ LLVM_TAG: -14
steps:
- name: Install prerequisites
diff --git a/README.md b/README.md
index f9ca8a9..611f2df 100644
--- a/README.md
+++ b/README.md
@@ -108,19 +108,23 @@ This weirdness is tracked in [issue 100](https://github.com/include-what-you-use
The original design was built for Make, but a number of alternative run modes have come up over the years.
-#### Plugging into Make ####
+#### Running on single source file ####
-The easiest way to run IWYU over your codebase is to run
+The simplest way to use IWYU is to run it against a single source file:
- make -k CXX=/path/to/llvm/Debug+Asserts/bin/include-what-you-use
+ include-what-you-use $CXXFLAGS myfile.cc
-or
+where `$CXXFLAGS` are the flags you would normally pass to the compiler.
- make -k CXX=/path/to/llvm/Release/bin/include-what-you-use
+#### Plugging into existing build system ####
-(include-what-you-use always exits with an error code, so the build system knows it didn't build a .o file. Hence the need for `-k`.)
+Typically there is already a build system containing the relevant compiler flags for all source files. Replace your compiler with `include-what-you-use` to generate a large batch of IWYU advice. Depending on your build system/build tools, this can take many forms, but for a simple GNU Make system it might look like this:
-Include-what-you-use only analyzes .cc (or .cpp) files built by `make`, along with their corresponding .h files. If your project has a .h file with no corresponding .cc file, IWYU will ignore it unless you use the `--check_also` switch to add it for analysis together with a .cc file.
+ make -k CXX=include-what-you-use CXXFLAGS="-Xiwyu --error_always"
+
+(The additional `-Xiwyu --error_always` switch makes `include-what-you-use` always exit with an error code, so the build system knows it didn't build a .o file. Hence the need for `-k`.)
+
+In this mode `include-what-you-use` only analyzes the .cc (or .cpp) files known to your build system, along with their corresponding .h files. If your project has a .h file with no corresponding .cc file, IWYU will ignore it unless you use the `--check_also` switch to add it for analysis together with a .cc file. It is possible to run IWYU against individual header files, provided the compiler flags are carefully constructed to match all includers.
#### Using with CMake ####
@@ -170,7 +174,7 @@ See `iwyu_tool.py --help` for more options.
We also include a tool that automatically fixes up your source files based on the IWYU recommendations. This is also alpha-quality software! Here's how to use it (requires python):
- make -k CXX=/path/to/llvm/Debug+Asserts/bin/include-what-you-use 2> /tmp/iwyu.out
+ make -k CXX=include-what-you-use CXXFLAGS="-Xiwyu --error_always" 2> /tmp/iwyu.out
python fix_includes.py < /tmp/iwyu.out
If you don't like the way `fix_includes.py` munges your `#include` lines, you can control its behavior via flags. `fix_includes.py --help` will give a full list, but these are some common ones:
diff --git a/fix_includes.py b/fix_includes.py
index 8de5775..69b7003 100755
--- a/fix_includes.py
+++ b/fix_includes.py
@@ -82,9 +82,7 @@ All files mentioned in the include-what-you-use script are modified,
unless filenames are specified on the commandline, in which case only
those files are modified.
-The exit code is the number of files that were modified (or that would
-be modified if --dry_run was specified) unless that number exceeds 100,
-in which case 100 is returned.
+The exit code is non-zero if a critical error occurs, otherwise zero.
"""
_COMMENT_RE = re.compile(r'\s*//.*')
@@ -2453,11 +2451,12 @@ def main(argv):
if flags.sort_only:
if not files_to_modify:
sys.exit('FATAL ERROR: -s flag requires a list of filenames')
- return SortIncludesInFiles(files_to_modify, flags)
+ SortIncludesInFiles(files_to_modify, flags)
else:
- return ProcessIWYUOutput(sys.stdin, files_to_modify, flags, cwd=os.getcwd())
+ ProcessIWYUOutput(sys.stdin, files_to_modify, flags, cwd=os.getcwd())
+
+ return 0
if __name__ == '__main__':
- num_files_fixed = main(sys.argv)
- sys.exit(min(num_files_fixed, 100))
+ sys.exit(main(sys.argv))
diff --git a/include-what-you-use.1 b/include-what-you-use.1
index c9376c0..9114f99 100644
--- a/include-what-you-use.1
+++ b/include-what-you-use.1
@@ -1,10 +1,10 @@
-.\" t -*- coding: UTF-8 -*-
+.\" t -*- coding: utf-8 -*-
.\" Man page for include-what-you-use
.\"
.\" This file is distributed under the University of Illinois Open Source
.\" License. See LICENSE.TXT for details.
.\"
-.TH INCLUDE-WHAT-YOU-USE 1 "2019-11-02" include-what-you-use "User Commands"
+.TH INCLUDE-WHAT-YOU-USE 1 "2022-02-21" include-what-you-use "User Commands"
.SH NAME
include-what-you-use \- analyze includes in C and C++ source files.
.SH SYNOPSIS
@@ -47,6 +47,18 @@ This flag may be specified multiple times to specify multiple glob patterns.
.B \-\-cxx17ns
Suggest the more concise syntax for nested namespaces introduced in C++17.
.TP
+.BI \-\-error [=N]
+Exit with error code
+.IR N
+(defaults to 1 if omitted) if there are \(lqinclude-what-you-use\(rq
+violations.
+.TP
+.BI \-\-error_always [=N]
+Exit with error code
+.IR N
+(defaults to 1 if omitted) whether there are \(lqinclude-what-you-use\(rq
+violations or not (for use with \fBmake(1)\fR).
+.TP
.BI \-\-keep= glob
Always keep the includes matched by
.IR glob .
@@ -113,10 +125,16 @@ is already visible in the file's transitive includes.
Set verbosity. At the highest level, this will dump the AST of the source file
and explain all decisions.
.SH EXIT STATUS
+By default
.B include-what-you-use
-always returns with a nonzero status code to make usage with
-.BR make (1)
-feasible.
+exits with zero exit code unless there's a critical error, but
+.B \-\-error
+or
+.B \-\-error_always
+can be used to customize the exit code depending on invoker expectations.
+See
+.IR EXAMPLE\fR.
+
.SH MAPPING FILES
Sometimes headers are not meant to be included directly,
and sometimes headers are guaranteed to include other headers.
@@ -240,18 +258,22 @@ issue tracker
.UE
on GitHub.
.SH EXAMPLE
-The easiest way to run
+It is possible to put
.B include-what-you-use
-over your codebase is to run
+in place of your compiler to process all source files known to your build system
.PP
.RS
.EX
-make \-k CC=include-what-you-use CXX=include-what-you-use
+make \-k CC=include-what-you-use CFLAGS="-Xiwyu --error_always"
+.EE
+
+.EX
+make \-k CXX=include-what-you-use CXXFLAGS="-Xiwyu --error_always"
.EE
.RE
.PP
-The program always exits with an error code, so the build system knows that it
-didn't build an object file. Hence the need for
+With \fB-Xiwyu --error_always\fR the program always exits with an error code, so
+the build system knows that it didn't build an object file. Hence the need for
.BR -k .
It only analyzes source files built by
.BR make (1)
diff --git a/iwyu.cc b/iwyu.cc
index 20fed76..266bc45 100644
--- a/iwyu.cc
+++ b/iwyu.cc
@@ -1345,6 +1345,15 @@ class IwyuBaseAstVisitor : public BaseAstVisitor<Derived> {
if (decl == nullptr) // only class-types are candidates for returning true
return false;
+ // Sometimes a type points back to an implicit decl (e.g. a bultin type),
+ // and we can't do author-intent analysis without location information.
+ // Assume that it's not forward-declarable.
+ if (decl->isImplicit()) {
+ VERRS(5) << "Skipping forward-declare analysis for implicit decl: '"
+ << PrintableDecl(decl) << "'\n";
+ return false;
+ }
+
// If we're a template specialization, we also accept
// forward-declarations of the underlying template (vector<T>, not
// vector<int>).
@@ -1901,7 +1910,7 @@ class IwyuBaseAstVisitor : public BaseAstVisitor<Derived> {
// If this cast requires a user-defined conversion of the from-type, look up
// its return type so we can see through up/down-casts via such conversions.
const Type* converted_from_type = nullptr;
- if (const NamedDecl* conv_decl = expr->getConversionFunction()) {
+ if (const NamedDecl* conv_decl = GetConversionFunction(expr)) {
converted_from_type =
cast<FunctionDecl>(conv_decl)->getReturnType().getTypePtr();
}
@@ -2383,12 +2392,10 @@ class IwyuBaseAstVisitor : public BaseAstVisitor<Derived> {
// that, and is clearly a c++ path, is fine; its exact
// contents don't matter that much.
using clang::Optional;
- using clang::DirectoryLookup;
using clang::FileEntryRef;
const FileEntry* use_file = CurrentFileEntry();
- const DirectoryLookup* curdir = nullptr;
Optional<FileEntryRef> file = compiler()->getPreprocessor().LookupFile(
- CurrentLoc(), "new", true, nullptr, use_file, curdir, nullptr,
+ CurrentLoc(), "new", true, nullptr, use_file, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr, false);
if (file) {
preprocessor_info().FileInfoFor(use_file)->ReportFullSymbolUse(
@@ -3644,14 +3651,8 @@ class IwyuAstConsumer
// Check if any unrecoverable errors have occurred.
// There is no point in continuing when the AST is in a bad state.
- //
- // EXIT_INVALIDARGS is not a great choice for the return status
- // because a compile error will not have a strong connection to the
- // command line arguments, but there are only 2 error codes and
- // this is the least bad choice.
- // TODO : Readdress when error codes are reworked.
if (compiler()->getDiagnostics().hasUnrecoverableErrorOccurred())
- exit(EXIT_INVALIDARGS);
+ exit(EXIT_FAILURE);
const set<const FileEntry*>* const files_to_report_iwyu_violations_for
= preprocessor_info().files_to_report_iwyu_violations_for();
@@ -3681,8 +3682,16 @@ class IwyuAstConsumer
num_edits += preprocessor_info().FileInfoFor(main_file)
->CalculateAndReportIwyuViolations();
- // We need to force the compile to fail so we can re-run.
- exit(EXIT_SUCCESS_OFFSET + num_edits);
+ int exit_code = EXIT_SUCCESS;
+ if (GlobalFlags().exit_code_always) {
+ // If we should always fail, use --error_always value.
+ exit_code = GlobalFlags().exit_code_always;
+ } else if (num_edits > 0) {
+ // If there were IWYU violations, use --error value.
+ exit_code = GlobalFlags().exit_code_error;
+ }
+
+ exit(exit_code);
}
void ParseFunctionTemplates(Sema& sema, TranslationUnitDecl* tu_decl) {
@@ -4043,6 +4052,20 @@ class IwyuAstConsumer
return Base::VisitTypedefType(type);
}
+ bool VisitUsingType(clang::UsingType* type) {
+ if (CanIgnoreCurrentASTNode())
+ return true;
+
+ // UsingType is similar to TypedefType, so treat it the same.
+ if (CanForwardDeclareType(current_ast_node())) {
+ ReportDeclForwardDeclareUse(CurrentLoc(), type->getFoundDecl());
+ } else {
+ ReportDeclUse(CurrentLoc(), type->getFoundDecl());
+ }
+
+ return Base::VisitUsingType(type);
+ }
+
// This is a superclass of RecordType and CXXRecordType.
bool VisitTagType(clang::TagType* type) {
if (CanIgnoreCurrentASTNode()) return true;
@@ -4191,6 +4214,8 @@ using include_what_you_use::IwyuAction;
using include_what_you_use::CreateCompilerInstance;
int main(int argc, char **argv) {
+ llvm::llvm_shutdown_obj scoped_shutdown;
+
// X86 target is required to parse Microsoft inline assembly, so we hope it's
// part of all targets. Clang parser will complain otherwise.
llvm::InitializeAllTargetInfos();
@@ -4198,21 +4223,19 @@ int main(int argc, char **argv) {
llvm::InitializeAllAsmParsers();
// The command line should look like
- // path/to/iwyu -Xiwyu --verbose=4 [-Xiwyu --other_iwyu_flag]... CLANG_FLAGS... foo.cc
+ // path/to/iwyu -Xiwyu --verbose=4 [-Xiwyu --other_iwyu_flag]... \
+ // CLANG_FLAGS... foo.cc
OptionsParser options_parser(argc, argv);
std::unique_ptr<clang::CompilerInstance> compiler(CreateCompilerInstance(
options_parser.clang_argc(), options_parser.clang_argv()));
- if (compiler) {
- // Create and execute the frontend to generate an LLVM bitcode module.
- std::unique_ptr<clang::ASTFrontendAction> action(new IwyuAction);
- compiler->ExecuteAction(*action);
+ if (!compiler) {
+ return EXIT_FAILURE;
}
- llvm::llvm_shutdown();
+ // Create and execute the frontend to generate an LLVM bitcode module.
+ std::unique_ptr<clang::ASTFrontendAction> action(new IwyuAction);
+ compiler->ExecuteAction(*action);
- // We always return a failure exit code, to indicate we didn't
- // successfully compile (produce a .o for) the source files we were
- // given.
- return 1;
+ return EXIT_SUCCESS;
}
diff --git a/iwyu_ast_util.cc b/iwyu_ast_util.cc
index 81906aa..48acb5b 100644
--- a/iwyu_ast_util.cc
+++ b/iwyu_ast_util.cc
@@ -32,6 +32,7 @@
#include "clang/AST/DeclCXX.h"
#include "clang/AST/DeclTemplate.h"
#include "clang/AST/ExprCXX.h"
+#include "clang/AST/IgnoreExpr.h"
#include "clang/AST/NestedNameSpecifier.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/AST/Stmt.h"
@@ -49,11 +50,14 @@ class FileEntry;
using clang::ASTDumper;
using clang::BlockPointerType;
+using clang::CastExpr;
+using clang::CXXBindTemporaryExpr;
using clang::CXXConstructExpr;
using clang::CXXConstructorDecl;
using clang::CXXDeleteExpr;
using clang::CXXDependentScopeMemberExpr;
using clang::CXXDestructorDecl;
+using clang::CXXMemberCallExpr;
using clang::CXXMethodDecl;
using clang::CXXNewExpr;
using clang::CXXRecordDecl;
@@ -74,12 +78,15 @@ using clang::EnumDecl;
using clang::Expr;
using clang::ExprWithCleanups;
using clang::FileEntry;
+using clang::FullExpr;
using clang::FullSourceLoc;
using clang::FunctionDecl;
using clang::FunctionType;
using clang::ImplicitCastExpr;
+using clang::IgnoreExprNodes;
using clang::InjectedClassNameType;
using clang::LValueReferenceType;
+using clang::MaterializeTemporaryExpr;
using clang::MemberExpr;
using clang::MemberPointerType;
using clang::NamedDecl;
@@ -1475,4 +1482,36 @@ TemplateArgumentListInfo GetExplicitTplArgs(const Expr* expr) {
return explicit_tpl_args;
}
+// This is lifted from CastExpr::getConversionFunction, and naively simplified
+// to work around bugs with consteval conversion functions.
+const NamedDecl* GetConversionFunction(const CastExpr* expr) {
+ const Expr *subexpr = nullptr;
+ for (const CastExpr *e = expr; e; e = dyn_cast<ImplicitCastExpr>(subexpr)) {
+ // Skip through implicit sema nodes.
+ subexpr = IgnoreExprNodes(e->getSubExpr(), [](Expr* expr) {
+ // FullExpr is ConstantExpr + ExprWithCleanups.
+ if (auto* fe = dyn_cast<FullExpr>(expr))
+ return fe->getSubExpr();
+
+ if (auto* mte = dyn_cast<MaterializeTemporaryExpr>(expr))
+ return mte->getSubExpr();
+
+ if (auto* bte = dyn_cast<CXXBindTemporaryExpr>(expr))
+ return bte->getSubExpr();
+
+ return expr;
+ });
+
+ // Now resolve the conversion function depending on cast kind.
+ if (e->getCastKind() == clang::CK_ConstructorConversion)
+ return cast<CXXConstructExpr>(subexpr)->getConstructor();
+
+ if (e->getCastKind() == clang::CK_UserDefinedConversion) {
+ if (auto *MCE = dyn_cast<CXXMemberCallExpr>(subexpr))
+ return MCE->getMethodDecl();
+ }
+ }
+ return nullptr;
+}
+
} // namespace include_what_you_use
diff --git a/iwyu_ast_util.h b/iwyu_ast_util.h
index 28668f7..f4f81a0 100644
--- a/iwyu_ast_util.h
+++ b/iwyu_ast_util.h
@@ -816,6 +816,10 @@ const clang::FunctionType* GetCalleeFunctionType(clang::CallExpr* expr);
// such a concept (declrefexpr, memberexpr), and empty list if none is present.
clang::TemplateArgumentListInfo GetExplicitTplArgs(const clang::Expr* expr);
+// Workaround for https://github.com/llvm/llvm-project/issues/53044. Remove this
+// wrapper in favor of Expr::getConversionFunction when that is fixed upstream.
+const clang::NamedDecl* GetConversionFunction(const clang::CastExpr* expr);
+
} // namespace include_what_you_use
#endif // INCLUDE_WHAT_YOU_USE_IWYU_AST_UTIL_H_
diff --git a/iwyu_globals.cc b/iwyu_globals.cc
index 96b728c..8ba6c05 100644
--- a/iwyu_globals.cc
+++ b/iwyu_globals.cc
@@ -98,6 +98,9 @@ static void PrintHelp(const char* extra_msg) {
" --quoted_includes_first: when sorting includes, place quoted\n"
" ones first.\n"
" --cxx17ns: suggests the more concise syntax introduced in C++17\n"
+ " --error[=N]: exit with N (default: 1) for iwyu violations\n"
+ " --error_always[=N]: always exit with N (default: 1) (for use\n"
+ " with 'make -k')\n"
"\n"
"In addition to IWYU-specific options you can specify the following\n"
"options without -Xiwyu prefix:\n"
@@ -118,6 +121,22 @@ static void PrintVersion() {
<< "\n";
}
+static bool ParseIntegerOptarg(const char* optarg, int* res) {
+ char* endptr = nullptr;
+ long val = strtol(optarg, &endptr, 10);
+ if (!endptr || endptr == optarg)
+ return false;
+
+ if (*endptr != '\0')
+ return false;
+
+ if (val > INT_MAX || val < INT_MIN)
+ return false;
+
+ *res = (int)val;
+ return true;
+}
+
OptionsParser::OptionsParser(int argc, char** argv) {
// Separate out iwyu-specific, intercepted, and clang flags. iwyu-specific
// flags are "-Xiwyu <iwyu_flag>", intercepted flags are usual clang flags
@@ -168,7 +187,9 @@ CommandlineFlags::CommandlineFlags()
update_comments(false),
no_fwd_decls(false),
quoted_includes_first(false),
- cxx17ns(false) {
+ cxx17ns(false),
+ exit_code_error(EXIT_SUCCESS),
+ exit_code_always(EXIT_SUCCESS) {
// Always keep Qt .moc includes; its moc compiler does its own IWYU analysis.
keep.emplace("*.moc");
}
@@ -189,6 +210,8 @@ int CommandlineFlags::ParseArgv(int argc, char** argv) {
{"no_fwd_decls", no_argument, nullptr, 'f'},
{"quoted_includes_first", no_argument, nullptr, 'q' },
{"cxx17ns", no_argument, nullptr, 'C'},
+ {"error", optional_argument, nullptr, 'e'},
+ {"error_always", optional_argument, nullptr, 'a'},
{nullptr, 0, nullptr, 0}
};
static const char shortopts[] = "v:c:m:n";
@@ -212,7 +235,7 @@ int CommandlineFlags::ParseArgv(int argc, char** argv) {
prefix_header_include_policy = CommandlineFlags::kRemove;
} else {
PrintHelp("FATAL ERROR: unknown --prefix_header_includes value.");
- exit(EXIT_INVALIDARGS);
+ exit(EXIT_FAILURE);
}
break;
case 'h': pch_in_code = true; break;
@@ -222,14 +245,33 @@ int CommandlineFlags::ParseArgv(int argc, char** argv) {
break;
case 'q': quoted_includes_first = true; break;
case 'C': cxx17ns = true; break;
+ case 'e':
+ if (!optarg) {
+ exit_code_error = EXIT_FAILURE;
+ } else if (!ParseIntegerOptarg(optarg, &exit_code_error)) {
+ PrintHelp("FATAL ERROR: --error argument must be valid integer.");
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case 'a':
+ if (!optarg) {
+ exit_code_always = EXIT_FAILURE;
+ } else if (!ParseIntegerOptarg(optarg, &exit_code_always)) {
+ PrintHelp(
+ "FATAL ERROR: --error_always argument must be valid "
+ "integer.");
+ exit(EXIT_FAILURE);
+ }
+ break;
case -1: return optind; // means 'no more input'
default:
PrintHelp("FATAL ERROR: unknown flag.");
- exit(EXIT_INVALIDARGS);
+ exit(EXIT_FAILURE);
break;
}
}
- return optind; // unreachable
+
+ CHECK_UNREACHABLE_("All switches should be handled above");
}
// Though option -v prints version too, it isn't intercepted because it also
@@ -251,7 +293,7 @@ static int ParseInterceptedCommandlineFlags(int argc, char** argv) {
case -1: return optind; // means 'no more input'
default:
PrintHelp("FATAL ERROR: unknown flag.");
- exit(EXIT_INVALIDARGS);
+ exit(EXIT_FAILURE);
break;
}
}
diff --git a/iwyu_globals.h b/iwyu_globals.h
index eefb8ce..8066890 100644
--- a/iwyu_globals.h
+++ b/iwyu_globals.h
@@ -24,15 +24,6 @@ struct PrintingPolicy;
namespace include_what_you_use {
-// Exit codes.
-// If invalid args are specified in any form, we return 1,
-// otherwise we return 2 + the number of edits suggested.
-// Of course, this means that IWYU always fails (i.e. never returns 0.)
-// This is intentional, so it can be used with make -k without ever being
-// considered up-to-date.
-static const int EXIT_INVALIDARGS = 1;
-static const int EXIT_SUCCESS_OFFSET = 2;
-
using std::set;
using std::string;
using std::vector;
@@ -102,6 +93,8 @@ struct CommandlineFlags {
bool no_fwd_decls; // Disable forward declarations.
bool quoted_includes_first; // Place quoted includes first in sort order.
bool cxx17ns; // -C: C++17 nested namespace syntax
+ int exit_code_error; // Exit with this code for iwyu violations.
+ int exit_code_always; // Always exit with this exit code.
};
const CommandlineFlags& GlobalFlags();
diff --git a/iwyu_preprocessor.cc b/iwyu_preprocessor.cc
index 6dc70bf..10a1ad0 100644
--- a/iwyu_preprocessor.cc
+++ b/iwyu_preprocessor.cc
@@ -624,9 +624,7 @@ void IwyuPreprocessorInfo::MacroDefined(const Token& id,
// #undefs and re-defines a macro, but should work fine in practice.)
if (macro_loc.isValid())
macros_definition_loc_[GetName(id)] = macro_loc;
- for (MacroInfo::tokens_iterator it = macro->tokens_begin();
- it != macro->tokens_end(); ++it) {
- const Token& token_in_macro = *it;
+ for (const Token& token_in_macro : macro->tokens()) {
if (token_in_macro.getKind() == clang::tok::identifier &&
token_in_macro.getIdentifierInfo()->hasMacroDefinition()) {
macros_called_from_macros_.push_back(token_in_macro);
diff --git a/iwyu_test_util.py b/iwyu_test_util.py
index 9bffb31..f117d66 100755
--- a/iwyu_test_util.py
+++ b/iwyu_test_util.py
@@ -39,19 +39,22 @@ _ACTUAL_DIAGNOSTICS_RE = re.compile(r'^(.*?):(\d+):\d+:\s*'
# This is the final summary output that iwyu.cc produces when --verbose >= 1
# The summary for a given source file should appear in that source file,
# surrounded by '/**** IWYU_SUMMARY' and '***** IWYU_SUMMARY */'.
+# The leading summary line may also have an expected exit-code in parentheses
+# after the summary marker: '/**** IWYU_SUMMARY(10)'.
_EXPECTED_SUMMARY_START_RE = re.compile(r'/\*+\s*IWYU_SUMMARY')
+_EXPECTED_SUMMARY_EXIT_CODE_RE = re.compile(r'/\*+\s*IWYU_SUMMARY\((\d+)\)')
_EXPECTED_SUMMARY_END_RE = re.compile(r'\**\s*IWYU_SUMMARY\s*\*+/')
_ACTUAL_SUMMARY_START_RE = re.compile(r'^(.*?) should add these lines:$')
_ACTUAL_SUMMARY_END_RE = re.compile(r'^---$')
_ACTUAL_REMOVAL_LIST_START_RE = re.compile(r'.* should remove these lines:$')
_NODIFFS_RE = re.compile(r'^\((.*?) has correct #includes/fwd-decls\)$')
-# This is an IWYU_ARGS line that specifies launch arguments
-# for a test in its source file.
-# Example:
+# This is an IWYU_ARGS line that specifies launch arguments for a test in its
+# source file. Example:
# // IWYU_ARGS: -Xiwyu --mapping_file=... -I .
_IWYU_TEST_RUN_ARGS_RE = re.compile(r'^//\sIWYU_ARGS:\s(.*)$')
+
def _PortableNext(iterator):
if hasattr(iterator, 'next'):
iterator.next() # Python 2.4-2.6
@@ -128,7 +131,7 @@ def _GetCommandOutput(command):
stdout, _ = p.communicate()
lines = stdout.decode("utf-8").splitlines(True)
lines = [line.replace(os.linesep, '\n') for line in lines]
- return lines
+ return p.returncode, lines
def _GetMatchingLines(regex, file_names):
@@ -290,6 +293,16 @@ def _GetExpectedSummaries(files):
return expected_summaries
+def _GetExpectedExitCode(main_file):
+ with open(main_file, 'r') as fh:
+ for line in fh:
+ m = _EXPECTED_SUMMARY_EXIT_CODE_RE.match(line)
+ if m:
+ res = int(m.group(1))
+ return res
+ return None
+
+
def _GetActualSummaries(output):
"""Returns a map: source file => list of iwyu summary lines."""
@@ -443,7 +456,6 @@ def TestIwyuOnRelativeFile(cc_file, cpp_files_to_check, verbose=False):
if env_verbose_level:
verbosity_flags = ['-Xiwyu', '--verbose=' + env_verbose_level]
- # TODO(csilvers): verify that has exit-status 0.
cmd = '%s %s %s %s %s' % (
_ShellQuote(_GetIwyuPath()),
# Require verbose level 3 so that we can verify the individual diagnostics.
@@ -457,10 +469,16 @@ def TestIwyuOnRelativeFile(cc_file, cpp_files_to_check, verbose=False):
cc_file)
if verbose:
print('>>> Running %s' % cmd)
- output = _GetCommandOutput(cmd)
+ exit_code, output = _GetCommandOutput(cmd)
print(''.join(output))
sys.stdout.flush() # don't commingle this output with the failure output
+ # Verify exit code if requested
+ expected_exit_code = _GetExpectedExitCode(cc_file)
+ if expected_exit_code is not None and exit_code != expected_exit_code:
+ raise AssertionError('Unexpected exit code, wanted %d, was %d' %
+ (expected_exit_code, exit_code))
+
expected_diagnostics = _GetMatchingLines(
_EXPECTED_DIAGNOSTICS_RE, cpp_files_to_check)
failures = _CompareExpectedAndActualDiagnostics(
diff --git a/iwyu_tool.py b/iwyu_tool.py
index 45b2a4a..fcb210e 100755
--- a/iwyu_tool.py
+++ b/iwyu_tool.py
@@ -367,8 +367,7 @@ def execute(invocations, verbose, formatter, jobs, max_load_average=0):
for invocation in invocations:
proc = invocation.start(verbose)
print(formatter(proc.get_output()))
- if proc.returncode != 2:
- exit_code = 1
+ exit_code = max(exit_code, proc.returncode)
return exit_code
pending = []
@@ -378,8 +377,7 @@ def execute(invocations, verbose, formatter, jobs, max_load_average=0):
for proc in complete:
pending.remove(proc)
print(formatter(proc.get_output()))
- if proc.returncode != 2:
- exit_code = 1
+ exit_code = max(exit_code, proc.returncode)
# Schedule new processes if there's room.
capacity = jobs - len(pending)
diff --git a/iwyu_tool_test.py b/iwyu_tool_test.py
index 4dbfdac..6d0864b 100755
--- a/iwyu_tool_test.py
+++ b/iwyu_tool_test.py
@@ -64,7 +64,11 @@ class MockInvocation(iwyu_tool.Invocation):
class MockIwyuToolMain(object):
""" Replacement for iwyu_tool.main to capture parsed arguments. """
def __init__(self):
- self.argspec = inspect.getargspec(iwyu_tool.main).args
+ if hasattr(inspect, 'getfullargspec'):
+ getargspec = inspect.getfullargspec
+ else:
+ getargspec = inspect.getargspec
+ self.argspec = getargspec(iwyu_tool.main).args
self.real_iwyu_tool_main = iwyu_tool.main
iwyu_tool.main = self._mock
self.call_args = {}
@@ -131,35 +135,48 @@ class IWYUToolTests(unittest.TestCase):
def test_returncode(self):
invocation = MockInvocation()
- invocation.will_returncode(2)
+ invocation.will_returncode(0)
self.assertEqual(self._execute([invocation]), 0)
invocation = MockInvocation()
- invocation.will_returncode(7)
+ invocation.will_returncode(1)
self.assertEqual(self._execute([invocation]), 1)
+ invocation = MockInvocation()
+ invocation.will_returncode(2)
+ self.assertEqual(self._execute([invocation]), 2)
def test_returncode_asynchronous(self):
invocations = [MockInvocation() for _ in range(100)]
for invocation in invocations:
- invocation.will_returncode(2)
+ invocation.will_returncode(0)
invocation.will_block(random.random() / 100)
self.assertEqual(self._execute(invocations, jobs=100), 0)
invocations = [MockInvocation() for _ in range(100)]
+ for invocation in invocations:
+ invocation.will_returncode(2)
+ invocation.will_block(random.random() / 100)
+ self.assertEqual(self._execute(invocations, jobs=100), 2)
+ invocations = [MockInvocation() for _ in range(100)]
for n, invocation in enumerate(invocations):
invocation.will_returncode(6 if n == 0 else 2)
invocation.will_block(random.random() / 100)
- self.assertEqual(self._execute(invocations, jobs=100), 1)
+ self.assertEqual(self._execute(invocations, jobs=100), 6)
- def test_order_synchronous(self):
+ def test_returncode_synchronous(self):
invocations = [MockInvocation() for _ in range(1)]
for invocation in invocations:
- invocation.will_returncode(2)
+ invocation.will_returncode(0)
invocation.will_block(random.random() / 100)
self.assertEqual(self._execute(invocations, jobs=100), 0)
invocations = [MockInvocation() for _ in range(1)]
+ for invocation in invocations:
+ invocation.will_returncode(2)
+ invocation.will_block(random.random() / 100)
+ self.assertEqual(self._execute(invocations, jobs=100), 2)
+ invocations = [MockInvocation() for _ in range(1)]
for n, invocation in enumerate(invocations):
invocation.will_returncode(6 if n == 0 else 2)
invocation.will_block(random.random() / 100)
- self.assertEqual(self._execute(invocations, jobs=100), 1)
+ self.assertEqual(self._execute(invocations, jobs=100), 6)
@unittest.skipIf(sys.platform.startswith('win'), "POSIX only")
def test_is_subpath_of_posix(self):
diff --git a/iwyu_version.h b/iwyu_version.h
index 7c3b41c..66b7c62 100644
--- a/iwyu_version.h
+++ b/iwyu_version.h
@@ -10,6 +10,6 @@
#ifndef INCLUDE_WHAT_YOU_USE_IWYU_VERSION_H_
#define INCLUDE_WHAT_YOU_USE_IWYU_VERSION_H_
-#define IWYU_VERSION_STRING "0.17"
+#define IWYU_VERSION_STRING "0.18"
#endif // INCLUDE_WHAT_YOU_USE_IWYU_VERSION_H_
diff --git a/run_iwyu_tests.py b/run_iwyu_tests.py
index 9e7918f..1eeb139 100755
--- a/run_iwyu_tests.py
+++ b/run_iwyu_tests.py
@@ -132,6 +132,9 @@ if __name__ == '__main__':
@GenerateTests(rootdir='tests/cxx', pattern='*.cc')
class cxx(unittest.TestCase): pass
+ @GenerateTests(rootdir='tests/driver', pattern='*.c')
+ class driver(unittest.TestCase): pass
+
if runner_args.list_tests:
exit(PrintLoadedTests())
elif runner_args.list_test_files:
diff --git a/tests/cxx/consteval.cc b/tests/cxx/consteval.cc
new file mode 100644
index 0000000..3d4652b
--- /dev/null
+++ b/tests/cxx/consteval.cc
@@ -0,0 +1,51 @@
+//===--- consteval.cc - test input file for iwyu --------------------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+// IWYU_ARGS: -I . -std=c++20
+
+// These tests are not particularly interesting in themselves, but they cover an
+// upstream bug in Clang (https://github.com/llvm/llvm-project/issues/53044) for
+// which we have a workaround.
+
+#include "tests/cxx/direct.h"
+
+struct X {
+ // IWYU: IndirectClass needs a declaration
+ consteval X(const IndirectClass& v) {
+ }
+
+ // IWYU: IndirectClass needs a declaration
+ consteval operator IndirectClass*() const {
+ return nullptr;
+ }
+};
+
+void t() {
+ // Pass value through Consteval conversion constructor.
+ // IWYU: IndirectClass is...*indirect.h
+ IndirectClass a;
+ X x = a;
+
+ // Try an implicit consteval user-defined conversion too.
+ // IWYU: IndirectClass needs a declaration
+ IndirectClass* b = x;
+}
+
+/**** IWYU_SUMMARY
+
+tests/cxx/consteval.cc should add these lines:
+#include "tests/cxx/indirect.h"
+
+tests/cxx/consteval.cc should remove these lines:
+- #include "tests/cxx/direct.h" // lines XX-XX
+
+The full include-list for tests/cxx/consteval.cc:
+#include "tests/cxx/indirect.h" // for IndirectClass
+
+***** IWYU_SUMMARY */
diff --git a/tests/driver/direct.h b/tests/driver/direct.h
new file mode 100644
index 0000000..c4c6c78
--- /dev/null
+++ b/tests/driver/direct.h
@@ -0,0 +1,15 @@
+//===--- direct.h - test input file for iwyu ------------------------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef INCLUDE_WHAT_YOU_USE_TESTS_DRIVER_DIRECT_H_
+#define INCLUDE_WHAT_YOU_USE_TESTS_DRIVER_DIRECT_H_
+
+#include "tests/driver/indirect.h"
+
+#endif // INCLUDE_WHAT_YOU_USE_TESTS_DRIVER_DIRECT_H_
diff --git a/tests/driver/exitcode_bad_args.c b/tests/driver/exitcode_bad_args.c
new file mode 100644
index 0000000..4aaff4b
--- /dev/null
+++ b/tests/driver/exitcode_bad_args.c
@@ -0,0 +1,16 @@
+//===--- exitcode_bad_args.c - test input file for iwyu -------------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+// IWYU_ARGS: -some -unsupported -Xiwyu -arguments
+
+// When argument parsing fails, IWYU exits with code 1.
+
+/**** IWYU_SUMMARY(1)
+
+***** IWYU_SUMMARY */
diff --git a/tests/driver/exitcode_good.c b/tests/driver/exitcode_good.c
new file mode 100644
index 0000000..6eb542f
--- /dev/null
+++ b/tests/driver/exitcode_good.c
@@ -0,0 +1,16 @@
+//===--- exitcode_good.c - test input file for iwyu -----------------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+// Tests that IWYU exits with code 0 when no suggestions are made.
+
+/**** IWYU_SUMMARY(0)
+
+(tests/driver/exitcode_good.c has correct #includes/fwd-decls)
+
+***** IWYU_SUMMARY */
diff --git a/tests/driver/exitcode_good_error.c b/tests/driver/exitcode_good_error.c
new file mode 100644
index 0000000..440b3ed
--- /dev/null
+++ b/tests/driver/exitcode_good_error.c
@@ -0,0 +1,19 @@
+//===--- exitcode_good_error.c - test input file for iwyu -----------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+// IWYU_ARGS: -Xiwyu --error
+
+// When --error is provided, IWYU exits with error only if analysis finds IWYU
+// violations. In this case there are none, so IWYU exits with 0.
+
+/**** IWYU_SUMMARY(0)
+
+(tests/driver/exitcode_good_error.c has correct #includes/fwd-decls)
+
+***** IWYU_SUMMARY */
diff --git a/tests/driver/exitcode_good_error_always.c b/tests/driver/exitcode_good_error_always.c
new file mode 100644
index 0000000..475e014
--- /dev/null
+++ b/tests/driver/exitcode_good_error_always.c
@@ -0,0 +1,19 @@
+//===--- exitcode_good_error_always.c - test input file for iwyu ----------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+// IWYU_ARGS: -Xiwyu --error_always
+
+// When --error_always is provided, IWYU exits with error even if analysis
+// succeeds.
+
+/**** IWYU_SUMMARY(1)
+
+(tests/driver/exitcode_good_error_always.c has correct #includes/fwd-decls)
+
+***** IWYU_SUMMARY */
diff --git a/tests/driver/exitcode_good_error_always_arg.c b/tests/driver/exitcode_good_error_always_arg.c
new file mode 100644
index 0000000..b76c2fe
--- /dev/null
+++ b/tests/driver/exitcode_good_error_always_arg.c
@@ -0,0 +1,19 @@
+//===--- exitcode_good_error_always_arg.c - test input file for iwyu ------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+// IWYU_ARGS: -Xiwyu --error_always=100
+
+// When --error_always is provided, IWYU exits with error even if analysis
+// succeeds.
+
+/**** IWYU_SUMMARY(100)
+
+(tests/driver/exitcode_good_error_always_arg.c has correct #includes/fwd-decls)
+
+***** IWYU_SUMMARY */
diff --git a/tests/driver/exitcode_good_error_and_always.c b/tests/driver/exitcode_good_error_and_always.c
new file mode 100644
index 0000000..29310dc
--- /dev/null
+++ b/tests/driver/exitcode_good_error_and_always.c
@@ -0,0 +1,18 @@
+//===--- exitcode_good_error_and_always.c - test input file for iwyu ------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+// IWYU_ARGS: -Xiwyu --error=19 -Xiwyu --error_always=91
+
+// When both are provided, --error_always takes precedence
+
+/**** IWYU_SUMMARY(91)
+
+(tests/driver/exitcode_good_error_and_always.c has correct #includes/fwd-decls)
+
+***** IWYU_SUMMARY */
diff --git a/tests/driver/exitcode_good_error_arg.c b/tests/driver/exitcode_good_error_arg.c
new file mode 100644
index 0000000..093a754
--- /dev/null
+++ b/tests/driver/exitcode_good_error_arg.c
@@ -0,0 +1,19 @@
+//===--- exitcode_good_error_arg.c - test input file for iwyu -------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+// IWYU_ARGS: -Xiwyu --error=100
+
+// When --error is provided, IWYU exits with error only if analysis finds IWYU
+// violations. In this case there are none, so IWYU exits with 0.
+
+/**** IWYU_SUMMARY(0)
+
+(tests/driver/exitcode_good_error_arg.c has correct #includes/fwd-decls)
+
+***** IWYU_SUMMARY */
diff --git a/tests/driver/exitcode_syntax_error.c b/tests/driver/exitcode_syntax_error.c
new file mode 100644
index 0000000..6884c22
--- /dev/null
+++ b/tests/driver/exitcode_syntax_error.c
@@ -0,0 +1,19 @@
+//===--- exitcode_syntax_error.c - test input file for iwyu ---------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+// Tests that IWYU exits with code 1 when Clang fails to parse the provided
+// source code.
+
+// IWYU: expected ';' after top level declarator
+// IWYU: unknown type name 'this'
+this is not valid C code;
+
+/**** IWYU_SUMMARY(1)
+
+***** IWYU_SUMMARY */
diff --git a/tests/driver/exitcode_warn.c b/tests/driver/exitcode_warn.c
new file mode 100644
index 0000000..3cd3499
--- /dev/null
+++ b/tests/driver/exitcode_warn.c
@@ -0,0 +1,30 @@
+//===--- exitcode_warn.c - test input file for iwyu -----------------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+// IWYU_ARGS: -I .
+
+// By default, if IWYU finds policy violations it exits with code 0.
+
+#include "tests/driver/direct.h"
+
+// IWYU: Indirect is...*indirect.h
+struct Indirect x;
+
+/**** IWYU_SUMMARY(0)
+
+tests/driver/exitcode_warn.c should add these lines:
+#include "tests/driver/indirect.h"
+
+tests/driver/exitcode_warn.c should remove these lines:
+- #include "tests/driver/direct.h" // lines XX-XX
+
+The full include-list for tests/driver/exitcode_warn.c:
+#include "tests/driver/indirect.h" // for Indirect
+
+***** IWYU_SUMMARY */
diff --git a/tests/driver/exitcode_warn_error.c b/tests/driver/exitcode_warn_error.c
new file mode 100644
index 0000000..60fc1e3
--- /dev/null
+++ b/tests/driver/exitcode_warn_error.c
@@ -0,0 +1,31 @@
+//===--- exitcode_warn_error.c - test input file for iwyu -----------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+// IWYU_ARGS: -I . -Xiwyu --error
+
+// When --error is provided, IWYU exits with error if analysis finds IWYU
+// violations. Default exit code is 1.
+
+#include "tests/driver/direct.h"
+
+// IWYU: Indirect is...*indirect.h
+struct Indirect x;
+
+/**** IWYU_SUMMARY(1)
+
+tests/driver/exitcode_warn_error.c should add these lines:
+#include "tests/driver/indirect.h"
+
+tests/driver/exitcode_warn_error.c should remove these lines:
+- #include "tests/driver/direct.h" // lines XX-XX
+
+The full include-list for tests/driver/exitcode_warn_error.c:
+#include "tests/driver/indirect.h" // for Indirect
+
+***** IWYU_SUMMARY */
diff --git a/tests/driver/exitcode_warn_error_always.c b/tests/driver/exitcode_warn_error_always.c
new file mode 100644
index 0000000..b1e08f7
--- /dev/null
+++ b/tests/driver/exitcode_warn_error_always.c
@@ -0,0 +1,32 @@
+//===--- exitcode_warn_error_always.c - test input file for iwyu ----------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+// IWYU_ARGS: -I . -Xiwyu --error_always
+
+// When --error_always is provided, IWYU exits with error whether or not there
+// are IWYU violations. See exitcode_good_error_always.c for complementary
+// testcase where there are no violations.
+
+#include "tests/driver/direct.h"
+
+// IWYU: Indirect is...*indirect.h
+struct Indirect x;
+
+/**** IWYU_SUMMARY(1)
+
+tests/driver/exitcode_warn_error_always.c should add these lines:
+#include "tests/driver/indirect.h"
+
+tests/driver/exitcode_warn_error_always.c should remove these lines:
+- #include "tests/driver/direct.h" // lines XX-XX
+
+The full include-list for tests/driver/exitcode_warn_error_always.c:
+#include "tests/driver/indirect.h" // for Indirect
+
+***** IWYU_SUMMARY */
diff --git a/tests/driver/exitcode_warn_error_always_arg.c b/tests/driver/exitcode_warn_error_always_arg.c
new file mode 100644
index 0000000..4a2c590
--- /dev/null
+++ b/tests/driver/exitcode_warn_error_always_arg.c
@@ -0,0 +1,32 @@
+//===--- exitcode_warn_error_always_arg.c - test input file for iwyu ------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+// IWYU_ARGS: -I . -Xiwyu --error_always=11
+
+// When --error_always is provided, IWYU exits with error whether or not there
+// are IWYU violations. See exitcode_good_error_always_arg.c for complementary
+// testcase where there are no violations.
+
+#include "tests/driver/direct.h"
+
+// IWYU: Indirect is...*indirect.h
+struct Indirect x;
+
+/**** IWYU_SUMMARY(11)
+
+tests/driver/exitcode_warn_error_always_arg.c should add these lines:
+#include "tests/driver/indirect.h"
+
+tests/driver/exitcode_warn_error_always_arg.c should remove these lines:
+- #include "tests/driver/direct.h" // lines XX-XX
+
+The full include-list for tests/driver/exitcode_warn_error_always_arg.c:
+#include "tests/driver/indirect.h" // for Indirect
+
+***** IWYU_SUMMARY */
diff --git a/tests/driver/exitcode_warn_error_and_always.c b/tests/driver/exitcode_warn_error_and_always.c
new file mode 100644
index 0000000..0e730ee
--- /dev/null
+++ b/tests/driver/exitcode_warn_error_and_always.c
@@ -0,0 +1,30 @@
+//===--- exitcode_warn_error_and_always.c - test input file for iwyu ------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+// IWYU_ARGS: -I . -Xiwyu --error=15 -Xiwyu --error_always=51
+
+// When both are provided, --error_always takes precedence
+
+#include "tests/driver/direct.h"
+
+// IWYU: Indirect is...*indirect.h
+struct Indirect x;
+
+/**** IWYU_SUMMARY(51)
+
+tests/driver/exitcode_warn_error_and_always.c should add these lines:
+#include "tests/driver/indirect.h"
+
+tests/driver/exitcode_warn_error_and_always.c should remove these lines:
+- #include "tests/driver/direct.h" // lines XX-XX
+
+The full include-list for tests/driver/exitcode_warn_error_and_always.c:
+#include "tests/driver/indirect.h" // for Indirect
+
+***** IWYU_SUMMARY */
diff --git a/tests/driver/exitcode_warn_error_arg.c b/tests/driver/exitcode_warn_error_arg.c
new file mode 100644
index 0000000..835d45c
--- /dev/null
+++ b/tests/driver/exitcode_warn_error_arg.c
@@ -0,0 +1,31 @@
+//===--- exitcode_warn_error_arg.c - test input file for iwyu -------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+// IWYU_ARGS: -I . -Xiwyu --error=42
+
+// When --error is provided, IWYU exits with error if analysis finds IWYU
+// violations.
+
+#include "tests/driver/direct.h"
+
+// IWYU: Indirect is...*indirect.h
+struct Indirect x;
+
+/**** IWYU_SUMMARY(42)
+
+tests/driver/exitcode_warn_error_arg.c should add these lines:
+#include "tests/driver/indirect.h"
+
+tests/driver/exitcode_warn_error_arg.c should remove these lines:
+- #include "tests/driver/direct.h" // lines XX-XX
+
+The full include-list for tests/driver/exitcode_warn_error_arg.c:
+#include "tests/driver/indirect.h" // for Indirect
+
+***** IWYU_SUMMARY */
diff --git a/tests/driver/indirect.h b/tests/driver/indirect.h
new file mode 100644
index 0000000..cc93fcb
--- /dev/null
+++ b/tests/driver/indirect.h
@@ -0,0 +1,17 @@
+//===--- indirect.h - test input file for iwyu ----------------------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef INCLUDE_WHAT_YOU_USE_TESTS_DRIVER_INDIRECT_H_
+#define INCLUDE_WHAT_YOU_USE_TESTS_DRIVER_INDIRECT_H_
+
+struct Indirect {
+ int a;
+};
+
+#endif // INCLUDE_WHAT_YOU_USE_TESTS_DRIVER_INDIRECT_H_