Skip to content

Commit 3c9e345

Browse files
authored
[clang-doc] fix flaky test in clang-doc (#101387)
Fixes #97507 #96809 introduce non-determinstic behaviour in clang-doc which caused the output to be randomly ordered which lead to randomly failing test. This patch modify clang-doc to behave deterministically again by sorting the output by location or USR.
1 parent 847f9cb commit 3c9e345

File tree

6 files changed

+75
-27
lines changed

6 files changed

+75
-27
lines changed

clang-tools-extra/clang-doc/Representation.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,5 +384,12 @@ ClangDocContext::ClangDocContext(tooling::ExecutionContext *ECtx,
384384
}
385385
}
386386

387+
void ScopeChildren::sort() {
388+
llvm::sort(Namespaces.begin(), Namespaces.end());
389+
llvm::sort(Records.begin(), Records.end());
390+
llvm::sort(Functions.begin(), Functions.end());
391+
llvm::sort(Enums.begin(), Enums.end());
392+
llvm::sort(Typedefs.begin(), Typedefs.end());
393+
}
387394
} // namespace doc
388395
} // namespace clang

clang-tools-extra/clang-doc/Representation.h

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ struct Reference {
104104

105105
bool mergeable(const Reference &Other);
106106
void merge(Reference &&I);
107+
bool operator<(const Reference &Other) const { return Name < Other.Name; }
107108

108109
/// Returns the path for this Reference relative to CurrentPath.
109110
llvm::SmallString<64> getRelativeFilePath(const StringRef &CurrentPath) const;
@@ -145,6 +146,8 @@ struct ScopeChildren {
145146
std::vector<FunctionInfo> Functions;
146147
std::vector<EnumInfo> Enums;
147148
std::vector<TypedefInfo> Typedefs;
149+
150+
void sort();
148151
};
149152

150153
// A base struct for TypeInfos
@@ -245,6 +248,11 @@ struct Location {
245248
std::tie(Other.LineNumber, Other.Filename);
246249
}
247250

251+
bool operator!=(const Location &Other) const {
252+
return std::tie(LineNumber, Filename) !=
253+
std::tie(Other.LineNumber, Other.Filename);
254+
}
255+
248256
// This operator is used to sort a vector of Locations.
249257
// No specific order (attributes more important than others) is required. Any
250258
// sort is enough, the order is only needed to call std::unique after sorting
@@ -270,10 +278,12 @@ struct Info {
270278

271279
virtual ~Info() = default;
272280

281+
Info &operator=(Info &&Other) = default;
282+
273283
SymbolID USR =
274284
SymbolID(); // Unique identifier for the decl described by this Info.
275-
const InfoType IT = InfoType::IT_default; // InfoType of this particular Info.
276-
SmallString<16> Name; // Unqualified name of the decl.
285+
InfoType IT = InfoType::IT_default; // InfoType of this particular Info.
286+
SmallString<16> Name; // Unqualified name of the decl.
277287
llvm::SmallVector<Reference, 4>
278288
Namespace; // List of parent namespaces for this decl.
279289
std::vector<CommentInfo> Description; // Comment description of this decl.
@@ -312,6 +322,20 @@ struct SymbolInfo : public Info {
312322

313323
std::optional<Location> DefLoc; // Location where this decl is defined.
314324
llvm::SmallVector<Location, 2> Loc; // Locations where this decl is declared.
325+
326+
bool operator<(const SymbolInfo &Other) const {
327+
// Sort by declaration location since we want the doc to be
328+
// generated in the order of the source code.
329+
// If the declaration location is the same, or not present
330+
// we sort by defined location otherwise fallback to the extracted name
331+
if (Loc.size() > 0 && Other.Loc.size() > 0 && Loc[0] != Other.Loc[0])
332+
return Loc[0] < Other.Loc[0];
333+
334+
if (DefLoc && Other.DefLoc && *DefLoc != *Other.DefLoc)
335+
return *DefLoc < *Other.DefLoc;
336+
337+
return extractName() < Other.extractName();
338+
}
315339
};
316340

317341
// TODO: Expand to allow for documenting templating and default args.

clang-tools-extra/clang-doc/tool/ClangDocMain.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,22 @@ llvm::Error getHtmlAssetFiles(const char *Argv0,
205205
return getDefaultAssetFiles(Argv0, CDCtx);
206206
}
207207

208+
/// Make the output of clang-doc deterministic by sorting the children of
209+
/// namespaces and records.
210+
void sortUsrToInfo(llvm::StringMap<std::unique_ptr<doc::Info>> &USRToInfo) {
211+
for (auto &I : USRToInfo) {
212+
auto &Info = I.second;
213+
if (Info->IT == doc::InfoType::IT_namespace) {
214+
auto *Namespace = static_cast<clang::doc::NamespaceInfo *>(Info.get());
215+
Namespace->Children.sort();
216+
}
217+
if (Info->IT == doc::InfoType::IT_record) {
218+
auto *Record = static_cast<clang::doc::RecordInfo *>(Info.get());
219+
Record->Children.sort();
220+
}
221+
}
222+
}
223+
208224
int main(int argc, const char **argv) {
209225
llvm::sys::PrintStackTraceOnErrorSignal(argv[0]);
210226
std::error_code OK;
@@ -341,6 +357,8 @@ Example usage for a project using a compile commands database:
341357
if (Error)
342358
return 1;
343359

360+
sortUsrToInfo(USRToInfo);
361+
344362
// Ensure the root output directory exists.
345363
if (std::error_code Err = llvm::sys::fs::create_directories(OutDirectory);
346364
Err != std::error_code()) {

clang-tools-extra/test/clang-doc/basic-project.test

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
// See https://github.com/llvm/llvm-project/issues/97507.
2-
// UNSUPPORTED: target={{.*}}
3-
41
// RUN: rm -rf %t && mkdir -p %t/docs %t/build
52
// RUN: sed 's|$test_dir|%/S|g' %S/Inputs/basic-project/database_template.json > %t/build/compile_commands.json
63
// RUN: clang-doc --format=html --output=%t/docs --executor=all-TUs %t/build/compile_commands.json
@@ -61,13 +58,13 @@
6158
// HTML-SHAPE: <p>Defined at line 8 of file {{.*}}Shape.h</p>
6259
// HTML-SHAPE: <p> Provides a common interface for different types of shapes.</p>
6360
// HTML-SHAPE: <h2 id="Functions">Functions</h2>
64-
// HTML-SHAPE: <h3 id="{{([0-9A-F]{40})}}">~Shape</h3>
65-
// HTML-SHAPE: <p>public void ~Shape()</p>
66-
// HTML-SHAPE: <p>Defined at line 13 of file {{.*}}Shape.h</p>
6761
// HTML-SHAPE: <h3 id="{{([0-9A-F]{40})}}">area</h3>
6862
// HTML-SHAPE: <p>public double area()</p>
6963
// HTML-SHAPE: <h3 id="{{([0-9A-F]{40})}}">perimeter</h3>
7064
// HTML-SHAPE: <p>public double perimeter()</p>
65+
// HTML-SHAPE: <h3 id="{{([0-9A-F]{40})}}">~Shape</h3>
66+
// HTML-SHAPE: <p>public void ~Shape()</p>
67+
// HTML-SHAPE: <p>Defined at line 13 of file {{.*}}Shape.h</p>
7168

7269
// HTML-CALC: <h1>class Calculator</h1>
7370
// HTML-CALC: <p>Defined at line 8 of file {{.*}}Calculator.h</p>

clang-tools-extra/test/clang-doc/namespace.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -272,14 +272,16 @@ namespace AnotherNamespace {
272272
// HTML-GLOBAL-INDEX: <h1>Global Namespace</h1>
273273
// HTML-GLOBAL-INDEX: <h2 id="Namespaces">Namespaces</h2>
274274
// HTML-GLOBAL-INDEX: <li>@nonymous_namespace</li>
275-
// HTML-GLOBAL-INDEX: <li>PrimaryNamespace</li>
276275
// HTML-GLOBAL-INDEX: <li>AnotherNamespace</li>
276+
// HTML-GLOBAL-INDEX: <li>PrimaryNamespace</li>
277+
277278

278279
// MD-GLOBAL-INDEX: # Global Namespace
279280
// MD-GLOBAL-INDEX: ## Namespaces
280281
// MD-GLOBAL-INDEX: * [@nonymous_namespace](..{{[\/]}}@nonymous_namespace{{[\/]}}index.md)
281-
// MD-GLOBAL-INDEX: * [PrimaryNamespace](..{{[\/]}}PrimaryNamespace{{[\/]}}index.md)
282282
// MD-GLOBAL-INDEX: * [AnotherNamespace](..{{[\/]}}AnotherNamespace{{[\/]}}index.md)
283+
// MD-GLOBAL-INDEX: * [PrimaryNamespace](..{{[\/]}}PrimaryNamespace{{[\/]}}index.md)
284+
283285

284286
// MD-ALL-FILES: # All Files
285287
// MD-ALL-FILES: ## [@nonymous_namespace](@nonymous_namespace{{[\/]}}index.md)

clang-tools-extra/test/clang-doc/templates.cpp

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,23 @@ void ParamPackFunction(T... args);
1818
// CHECK: ---
1919
// CHECK-NEXT: USR: '{{([0-9A-F]{40})}}'
2020
// CHECK-NEXT: ChildFunctions:
21+
// CHECK-NEXT: - USR: '{{([0-9A-F]{40})}}'
22+
// CHECK-NEXT: Name: 'ParamPackFunction'
23+
// CHECK-NEXT: Location:
24+
// CHECK-NEXT: - LineNumber: 16
25+
// CHECK-NEXT: Filename: '{{.*}}'
26+
// CHECK-NEXT: Params:
27+
// CHECK-NEXT: - Type:
28+
// CHECK-NEXT: Name: 'T...'
29+
// CHECK-NEXT: QualName: 'T...'
30+
// CHECK-NEXT: Name: 'args'
31+
// CHECK-NEXT: ReturnType:
32+
// CHECK-NEXT: Type:
33+
// CHECK-NEXT: Name: 'void'
34+
// CHECK-NEXT: QualName: 'void'
35+
// CHECK-NEXT: Template:
36+
// CHECK-NEXT: Params:
37+
// CHECK-NEXT: - Contents: 'class... T'
2138
// CHECK-NEXT: - USR: '{{([0-9A-F]{40})}}'
2239
// CHECK-NEXT: Name: 'function'
2340
// CHECK-NEXT: DefLocation:
@@ -56,21 +73,4 @@ void ParamPackFunction(T... args);
5673
// CHECK-NEXT: Params:
5774
// CHECK-NEXT: - Contents: 'bool'
5875
// CHECK-NEXT: - Contents: '0'
59-
// CHECK-NEXT: - USR: '{{([0-9A-F]{40})}}'
60-
// CHECK-NEXT: Name: 'ParamPackFunction'
61-
// CHECK-NEXT: Location:
62-
// CHECK-NEXT: - LineNumber: 16
63-
// CHECK-NEXT: Filename: '{{.*}}'
64-
// CHECK-NEXT: Params:
65-
// CHECK-NEXT: - Type:
66-
// CHECK-NEXT: Name: 'T...'
67-
// CHECK-NEXT: QualName: 'T...'
68-
// CHECK-NEXT: Name: 'args'
69-
// CHECK-NEXT: ReturnType:
70-
// CHECK-NEXT: Type:
71-
// CHECK-NEXT: Name: 'void'
72-
// CHECK-NEXT: QualName: 'void'
73-
// CHECK-NEXT: Template:
74-
// CHECK-NEXT: Params:
75-
// CHECK-NEXT: - Contents: 'class... T'
7676
// CHECK-NEXT: ...

0 commit comments

Comments
 (0)