• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2018 Google, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.common.truth;
18 
19 import static com.google.common.base.Preconditions.checkNotNull;
20 import static com.google.common.base.Strings.padEnd;
21 import static java.lang.Math.max;
22 
23 import com.google.common.collect.ImmutableList;
24 import java.io.Serializable;
25 import org.jspecify.annotations.Nullable;
26 
27 /**
28  * A string key-value pair in a failure message, such as "expected: abc" or "but was: xyz."
29  *
30  * <p>Most Truth users will never interact with this type. It appears in the Truth API only as a
31  * parameter to methods like {@link Subject#failWithActual(Fact, Fact...)}, which are used only by
32  * custom {@code Subject} implementations.
33  *
34  * <p>If you are writing a custom {@code Subject}, see <a
35  * href="https://truth.dev/failure_messages">our tips on writing failure messages</a>.
36  */
37 public final class Fact implements Serializable {
38   /**
39    * Creates a fact with the given key and value, which will be printed in a format like "key:
40    * value." The value is converted to a string by calling {@code String.valueOf} on it.
41    */
fact(String key, @Nullable Object value)42   public static Fact fact(String key, @Nullable Object value) {
43     return new Fact(key, String.valueOf(value));
44   }
45 
46   /**
47    * Creates a fact with no value, which will be printed in the format "key" (with no colon or
48    * value).
49    *
50    * <p>In most cases, prefer {@linkplain #fact key-value facts}, which give Truth more flexibility
51    * in how to format the fact for display. {@code simpleFact} is useful primarily for:
52    *
53    * <ul>
54    *   <li>messages from no-arg assertions. For example, {@code isNotEmpty()} would generate the
55    *       fact "expected not to be empty"
56    *   <li>prose that is part of a larger message. For example, {@code contains()} sometimes
57    *       displays facts like "expected to contain: ..." <i>"but did not"</i> "though it did
58    *       contain: ..."
59    * </ul>
60    */
simpleFact(String key)61   public static Fact simpleFact(String key) {
62     return new Fact(key, null);
63   }
64 
65   final String key;
66   final @Nullable String value;
67 
Fact(String key, @Nullable String value)68   private Fact(String key, @Nullable String value) {
69     this.key = checkNotNull(key);
70     this.value = value;
71   }
72 
73   /**
74    * Returns a simple string representation for the fact. While this is used in the output of {@code
75    * TruthFailureSubject}, it's not used in normal failure messages, which automatically align facts
76    * horizontally and indent multiline values.
77    */
78   @Override
toString()79   public String toString() {
80     return value == null ? key : key + ": " + value;
81   }
82 
83   /**
84    * Formats the given messages and facts into a string for use as the message of a test failure. In
85    * particular, this method horizontally aligns the beginning of fact values.
86    */
makeMessage(ImmutableList<String> messages, ImmutableList<Fact> facts)87   static String makeMessage(ImmutableList<String> messages, ImmutableList<Fact> facts) {
88     int longestKeyLength = 0;
89     boolean seenNewlineInValue = false;
90     for (Fact fact : facts) {
91       if (fact.value != null) {
92         longestKeyLength = max(longestKeyLength, fact.key.length());
93         // TODO(cpovirk): Look for other kinds of newlines.
94         seenNewlineInValue |= fact.value.contains("\n");
95       }
96     }
97 
98     StringBuilder builder = new StringBuilder();
99     for (String message : messages) {
100       builder.append(message);
101       builder.append('\n');
102     }
103 
104     /*
105      * *Usually* the first fact is printed at the beginning of a new line. However, when this
106      * exception is the cause of another exception, that exception will print it starting after
107      * "Caused by: " on the same line. The other exception sometimes also reuses this message as its
108      * own message. In both of those scenarios, the first line doesn't start at column 0, so the
109      * horizontal alignment is thrown off.
110      *
111      * There's not much we can do about this, short of always starting with a newline (which would
112      * leave a blank line at the beginning of the message in the normal case).
113      */
114     for (Fact fact : facts) {
115       if (fact.value == null) {
116         builder.append(fact.key);
117       } else if (seenNewlineInValue) {
118         builder.append(fact.key);
119         builder.append(":\n");
120         builder.append(indent(fact.value));
121       } else {
122         builder.append(padEnd(fact.key, longestKeyLength, ' '));
123         builder.append(": ");
124         builder.append(fact.value);
125       }
126       builder.append('\n');
127     }
128     if (builder.length() > 0) {
129       builder.setLength(builder.length() - 1); // remove trailing \n
130     }
131     return builder.toString();
132   }
133 
indent(String value)134   private static String indent(String value) {
135     // We don't want to indent with \t because the text would align exactly with the stack trace.
136     // We don't want to indent with \t\t because it would be very far for people with 8-space tabs.
137     // Let's compromise and indent by 4 spaces, which is different than both 2- and 8-space tabs.
138     return "    " + value.replace("\n", "\n    ");
139   }
140 }
141