/*
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.errorprone.bugpatterns.formatstring;
import com.google.errorprone.CompilationTestHelper;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** {@link FormatString}Test */
@RunWith(JUnit4.class)
public class FormatStringTest {
private CompilationTestHelper compilationHelper;
@Before
public void setUp() {
compilationHelper = CompilationTestHelper.newInstance(FormatString.class, getClass());
}
private void testFormat(String expected, String formatString) throws Exception {
compilationHelper
.addSourceLines(
"Test.java",
"import java.util.Locale;",
"import java.io.PrintWriter;",
"import java.io.PrintStream;",
"import java.io.Console;",
"class Test {",
" void f() {",
" // BUG: Diagnostic contains: " + expected,
" " + formatString,
" }",
"}")
.doTest();
}
@Test
public void testDuplicateFormatFlags() throws Exception {
testFormat("duplicate format flags: +", "String.format(\"e = %++10.4f\", Math.E);");
}
@Test
public void testFormatFlagsConversionMismatch() throws Exception {
testFormat(
"format specifier '%b' is not compatible with the given flag(s): #",
"String.format(\"%#b\", Math.E);");
}
@Test
public void testIllegalFormatCodePoint() throws Exception {
testFormat("invalid Unicode code point: 110000", "String.format(\"%c\", 0x110000);");
}
@Test
public void testIllegalFormatConversion() throws Exception {
testFormat(
"illegal format conversion: 'java.lang.String' cannot be formatted using '%f'",
"String.format(\"%f\", \"abcd\");");
}
@Test
public void testIllegalFormatFlags() throws Exception {
testFormat("illegal format flags: -0", "String.format(\"%-010d\", 5);");
}
@Test
public void testIllegalFormatPrecision() throws Exception {
testFormat("illegal format precision: 1", "String.format(\"%.1c\", 'c');");
}
@Test
public void testIllegalFormatWidth() throws Exception {
testFormat("illegal format width: 1", "String.format(\"%1n\");");
}
@Test
public void testMissingFormatArgument() throws Exception {
testFormat("missing argument for format specifier '%<s'", "String.format(\"%<s\", \"test\");");
}
@Test
public void testMissingFormatWidth() throws Exception {
testFormat("missing format width: %-f", "String.format(\"e = %-f\", Math.E);");
}
@Test
public void testUnknownFormatConversion() throws Exception {
testFormat("unknown format conversion: 'r'", "String.format(\"%r\", \"hello\");");
}
@Test
public void testCStyleLongConversion() throws Exception {
testFormat("use %d for all integral types", "String.format(\"%l\", 42);");
testFormat("use %d for all integral types", "String.format(\"%ld\", 42);");
testFormat("use %d for all integral types", "String.format(\"%lld\", 42);");
testFormat("%f for all floating point ", "String.format(\"%lf\", 42);");
testFormat("%f for all floating point ", "String.format(\"%llf\", 42);");
}
@Test
public void missingArguments() throws Exception {
compilationHelper
.addSourceLines(
"Test.java",
"class Test {",
" void f() {",
" // BUG: Diagnostic contains: missing argument for format specifier '%s'",
" String.format(\"%s %s %s\", 42);",
" // BUG: Diagnostic contains: missing argument for format specifier '%s'",
" String.format(\"%s %s %s\", 42, 42);",
" String.format(\"%s %s %s\", 42, 42, 42);",
" }",
"}")
.doTest();
}
@Test
public void extraArguments() throws Exception {
compilationHelper
.addSourceLines(
"Test.java",
"class Test {",
" void f() {",
" String.format(\"%s %s\", 1, 2);",
" // BUG: Diagnostic contains: extra format arguments: used 2, provided 3",
" String.format(\"%s %s\", 1, 2, 3);",
" // BUG: Diagnostic contains: extra format arguments: used 2, provided 4",
" String.format(\"%s %s\", 1, 2, 3, 4);",
" // BUG: Diagnostic contains: extra format arguments: used 0, provided 1",
" String.format(\"{0}\", 1);",
" }",
"}")
.doTest();
}
@Test
public void negative() throws Exception {
compilationHelper
.addSourceLines(
"Test.java",
"class Test {",
" void f() {",
" String.format(\"%d\", 42);",
" String.format(\"%d\", 42L);",
" String.format(\"%f\", 42.0f);",
" String.format(\"%f\", 42.0d);",
" String.format(\"%s\", \"hello\");",
" String.format(\"%s\", 42);",
" String.format(\"%s\", (Object) null);",
" String.format(\"%s\", new Object());",
" String.format(\"%c\", 'c');",
" }",
"}")
.doTest();
}
@Test
public void testPrintfMethods() throws Exception {
testFormat("", "String.format(\"%d\", \"hello\");");
testFormat("", "String.format(Locale.ENGLISH, \"%d\", \"hello\");");
testFormat("", "new PrintWriter(System.err).format(\"%d\", \"hello\");");
testFormat("", "new PrintWriter(System.err).format(Locale.ENGLISH, \"%d\", \"hello\");");
testFormat("", "new PrintWriter(System.err).printf(\"%d\", \"hello\");");
testFormat("", "new PrintWriter(System.err).printf(Locale.ENGLISH, \"%d\", \"hello\");");
testFormat("", "new PrintStream(System.err).format(\"%d\", \"hello\");");
testFormat("", "new PrintStream(System.err).format(Locale.ENGLISH, \"%d\", \"hello\");");
testFormat("", "new PrintStream(System.err).printf(\"%d\", \"hello\");");
testFormat("", "new PrintStream(System.err).printf(Locale.ENGLISH, \"%d\", \"hello\");");
testFormat(
"", "new java.util.Formatter(System.err).format(Locale.ENGLISH, \"%d\", \"hello\");");
testFormat("", "System.console().printf(\"%d\", \"hello\");");
testFormat("", "System.console().format(\"%d\", \"hello\");");
testFormat("", "System.console().readLine(\"%d\", \"hello\");");
testFormat("", "System.console().readPassword(\"%d\", \"hello\");");
}
@Test
public void nullArgument() throws Exception {
compilationHelper
.addSourceLines(
"Test.java",
"class Test {",
" void f() {",
" String.format(\"%s %s\", null, null);",
" }",
"}")
.doTest();
}
@Test
public void javaUtilTime() throws Exception {
compilationHelper
.addSourceLines(
"Test.java",
"import java.time.Instant;",
"import java.time.LocalDateTime;",
"import java.time.ZoneId;",
"class Test {",
" void f() {",
" System.err.printf(\"%tY\",",
" LocalDateTime.ofInstant(Instant.now(), ZoneId.systemDefault()));",
" }",
"}")
.doTest();
}
}