Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- In C++, a call whose receiver's type can't be read no longer links to a method of a class nested in the class the receiver's name suggests, or of a class whose name only contains that name. Before, such a guess took any method of the call's name whose qualified name contained the class's name: protocolbuffers/protobuf's `descriptor->file()` in a template reached `Descriptor::ExtensionRange::file`, and facebook/rocksdb's `CompositeEnv::IsInstanceOf(name)` reached `CompositeEnvWrapper::IsInstanceOf`. A call written through a class, like `PessimisticTransaction::PopSavePoint()` in an override, now links to the method that class inherits, here `TransactionBaseImpl::PopSavePoint`. Re-index C++ projects after upgrading.
- In C++, an alias that names a class through a class derived from the one that declares it, like protocolbuffers/protobuf's `using ConformanceRequestSetting = BinaryAndJsonConformanceSuite::ConformanceRequestSetting;`, now leads to that class, `ConformanceTestSuite::ConformanceRequestSetting`. Before, an alias was followed only to a class declared under exactly the name it spells, so objects created through this one linked to the alias rather than the class. Re-index C++ projects after upgrading.
- In C++, a name written in a googletest `TEST_F` or `TEST_P` body now finds the test fixture's nested types, those of its base classes included, before anything in the surrounding namespace, as C++ does for the member function such a body is: facebook/rocksdb's `new TestUserDefinedIndexFactory()` links to the class nested in the fixture's base. A class declared inside a test body, or inside any function, is now only found from that function, so the local classes many RocksDB tests each declare under one name, like `FakeWF` or `MyManager`, no longer stand in for one another, and a call like `fwf->SetIOError(true)` reaches the `FakeWF` its own test declares. Re-index C++ projects after upgrading.
- In C and C++ tests, a name that several files declare now links to the one the test's own includes declare, when there is one: protocolbuffers/protobuf's C# generator tests call the `TestSourceDir()` that `googletest.h` declares, not another test helper's they never include; its upb tests call `upb_Arena_New()` and the rest of upb's C API in upb's own files, not the copies bundled for PHP and Ruby, which had left those calls unlinked; and facebook/rocksdb's C API test reaches `rocksdb_open` and its siblings in `db/c.cc`. Re-index C and C++ projects after upgrading.
- In C++, a declaration continued on a line that starts with `*`, like facebook/rocksdb's `const auto& el =` over `*reinterpret_cast<std::shared_ptr<EventListener>*>(jhandle);`, is now read, so calls such as `el->OnFlushCompleted(…)` link to `EventListener`'s methods; such a line used to be skipped as part of a comment. Code commented out in a `/* … */` block, and the text of a string literal, like protocolbuffers/protobuf's `[[deprecated("Please use SpaceUsedLong() instead")]]`, are no longer read as declarations, which had sent a call to the wrong method or to none. Re-index C++ projects after upgrading.
- In C++, a call with a comparison or a shift among its arguments, like protocolbuffers/protobuf's `p->Emit({{"declare", has_bit_word_index < 0 ? … : ""}, …}, "…")` or `WriteVarint(num << 3, s)`, is now read with the right number of arguments, so it links to the overload that fits rather than one taking fewer. A parameter or variable whose type has template arguments is now read even after another such parameter on the same line, like facebook/rocksdb's `const autovector<uint32_t>& cf_ids`, or with empty ones, like fmt's `parse_context<>& ctx`: calls on it link to that class's methods, and calls on a `std::vector` declared that way no longer link to a project class's method of the same name. Re-index C++ projects after upgrading.
- In C++, a call passing `nullptr` now links to the overload that takes a `std::nullptr_t`, as C++ picks it, rather than one taking a pointer, and a call no longer links to an overload declared `= delete`, which no call that compiles can reach: protocolbuffers/protobuf's `ptr.reset(nullptr)` on an `Arena::UniquePtr` reaches `reset(std::nullptr_t)`, and `field.Add("foo")` on a `RepeatedPtrField` reaches `Add(Element&&)` instead of the deleted `Add(const Element&)`. Re-index C++ projects after upgrading.
Expand Down
256 changes: 256 additions & 0 deletions __tests__/cpp-test-translation-unit.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
/**
* A C or C++ test is a program of its own, so a name it uses is one its
* translation unit declares. Of several same-named candidates, a test suite
* the test does not include is not one of them (protobuf's csharp generator
* tests called `TestSourceDir()`, which googletest.h declares, and
* test_util2.h's `TestUtil::TestSourceDir`, included by none of them, took
* the calls), and what the unit includes goes before what it doesn't (a test
* of the hpb backend calls upb's `upb_Arena_New()` from upb/mem/arena.h, not
* the copies php-upb.h and ruby-upb.h bundle; rocksdb's histogram test sleeps
* on a `SystemClock`, not on emulated_clock.h's `EmulatedSystemClock`). A
* test whose includes don't resolve (jemalloc's tests include a generated
* header) keeps the one candidate there is, and a definition in the test's
* own file still comes first (#1079).
*/
import { describe, it, expect, afterAll, beforeAll } from 'vitest';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { CodeGraph } from '../src';

let root = '';
let cg: CodeGraph;

beforeAll(async () => {
root = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-cpp-test-tu-'));
const files: Record<string, string> = {
// googletest.h declares TestSourceDir(); googletest.cc defines it.
'src/google/protobuf/testing/googletest.h': `#ifndef GOOGLE_PROTOBUF_TESTING_GOOGLETEST_H__
#define GOOGLE_PROTOBUF_TESTING_GOOGLETEST_H__

#include <string>

namespace google {
namespace protobuf {

std::string TestSourceDir();

} // namespace protobuf
} // namespace google

#endif
`,
'src/google/protobuf/testing/googletest.cc': `#include "google/protobuf/testing/googletest.h"

namespace google {
namespace protobuf {

std::string TestSourceDir() { return "src"; }

} // namespace protobuf
} // namespace google
`,
// A test suite's header no csharp test includes.
'src/google/protobuf/test_util2.h': `#ifndef GOOGLE_PROTOBUF_TEST_UTIL2_H__
#define GOOGLE_PROTOBUF_TEST_UTIL2_H__

#include <string>

namespace google {
namespace protobuf {
namespace TestUtil {

inline std::string TestSourceDir() { return "src/google/protobuf"; }

} // namespace TestUtil
} // namespace protobuf
} // namespace google

#endif
`,
'src/google/protobuf/compiler/csharp/csharp_bootstrap_unittest.cc': `#include <string>

#include "google/protobuf/testing/googletest.h"

namespace google {
namespace protobuf {
namespace compiler {
namespace csharp {

int GeneratedDescriptorsAreUpToDate() {
std::string dir = TestSourceDir();
return static_cast<int>(dir.size());
}

} // namespace csharp
} // namespace compiler
} // namespace protobuf
} // namespace google
`,
// A receiver whose type isn't read is guessed by its name, among the
// classes the test includes: rocksdb's histogram test sleeps on its
// clock, which no emulated_clock.h include of its declares.
'include/rocksdb/system_clock.h': `namespace rocksdb {

class SystemClock {
public:
virtual ~SystemClock() {}
virtual void SleepForMicroseconds(int micros) = 0;
};

class SystemClockWrapper : public SystemClock {
public:
void SleepForMicroseconds(int micros) override { target_->SleepForMicroseconds(micros); }

private:
SystemClock* target_;
};

} // namespace rocksdb
`,
'env/emulated_clock.h': `#include "rocksdb/system_clock.h"

namespace rocksdb {

class EmulatedSystemClock : public SystemClockWrapper {
public:
void SleepForMicroseconds(int micros) override {}
};

} // namespace rocksdb
`,
'test_util/mock_time_env.h': `#include <memory>

#include "rocksdb/system_clock.h"

namespace rocksdb {

class MockSystemClock : public SystemClockWrapper {
public:
void MockSleepForMicroseconds(int micros) {}
};

} // namespace rocksdb
`,
'monitoring/histogram_test.cc': `#include <memory>

#include "rocksdb/system_clock.h"
#include "test_util/mock_time_env.h"

namespace rocksdb {

namespace {
std::shared_ptr<MockSystemClock> clock = std::make_shared<MockSystemClock>();
} // namespace

void PopulateHistogram(int low, int high) {
for (int i = low; i <= high; i++) {
clock->SleepForMicroseconds(i);
}
}

} // namespace rocksdb
`,
// A test that declares the class it calls on: its own file comes first,
// whatever it includes (#1079).
'src/util/collector.h': `#include <string>

class Collector {
public:
std::string last_error() const { return error_; }

private:
std::string error_;
};
`,
// upb's arena, and the copy a bundled amalgamation carries.
'php/ext/google/protobuf/php-upb.h': `typedef struct upb_Arena upb_Arena;

static inline upb_Arena* upb_Arena_New(void) { return 0; }
`,
'upb/mem/arena.h': `typedef struct upb_Arena upb_Arena;

static inline upb_Arena* upb_Arena_New(void) { return 0; }
`,
'hpb/backend/upb/interop_test.cc': `#include "upb/mem/arena.h"

int ArenaInterop() {
upb_Arena* arena = upb_Arena_New();
return arena == 0;
}
`,
'src/util/own_collector_unittest.cc': `#include "util/collector.h"

namespace {
class Collector {
public:
int last_error() const { return 0; }
};
} // namespace

int OwnCollector() {
Collector collector;
return collector.last_error();
}
`,
// jemalloc's tests include a header the build generates, so nothing
// shows the test includes its helpers: the one helper there is stays.
'deps/jemalloc/test/src/SFMT.c': `unsigned gen_rand32(void* ctx) {
return 4;
}
`,
'deps/jemalloc/test/unit/SFMT.c': `#include "test/jemalloc_test.h"

unsigned test_gen_rand32(void* ctx) {
return gen_rand32(ctx);
}
`,
};
for (const [rel, content] of Object.entries(files)) {
fs.mkdirSync(path.dirname(path.join(root, rel)), { recursive: true });
fs.writeFileSync(path.join(root, rel), content);
}
cg = await CodeGraph.init(root, { index: true });
});

afterAll(() => {
cg?.close();
if (root) fs.rmSync(root, { recursive: true, force: true });
});

/** `qualifiedName (file)` of every call the file's symbols make. */
const callsFrom = (file: string): string[] => {
const ids = cg.getNodesInFile(file).map((n) => n.id);
return cg.getOutgoingEdgesFrom(ids).filter((e) => e.kind === 'calls').map((e) => {
const target = cg.getNode(e.target)!;
return `${target.qualifiedName} (${target.filePath})`;
});
};

describe('a C/C++ test links what its translation unit declares', () => {
it('passes over a test suite it does not include for the declaration it does', () => {
const calls = callsFrom('src/google/protobuf/compiler/csharp/csharp_bootstrap_unittest.cc');
expect(calls).toContain('google::protobuf::TestSourceDir (src/google/protobuf/testing/googletest.cc)');
expect(calls.some((c) => c.includes('test_util2.h'))).toBe(false);
});

it('guesses a receiver among the classes its includes declare', () => {
const calls = callsFrom('monitoring/histogram_test.cc');
expect(calls).toContain('rocksdb::SystemClock::SleepForMicroseconds (include/rocksdb/system_clock.h)');
expect(calls.some((c) => c.includes('emulated_clock.h'))).toBe(false);
});

it('calls the copy of a function its includes declare', () => {
expect(callsFrom('hpb/backend/upb/interop_test.cc')).toContain('upb_Arena_New (upb/mem/arena.h)');
});

it('still prefers a declaration in the test file itself', () => {
const calls = callsFrom('src/util/own_collector_unittest.cc');
expect(calls.some((c) => c.endsWith('(src/util/own_collector_unittest.cc)') && c.includes('last_error'))).toBe(true);
expect(calls.some((c) => c.includes('(src/util/collector.h)'))).toBe(false);
});

it('keeps the only candidate when its includes do not show the unit', () => {
expect(callsFrom('deps/jemalloc/test/unit/SFMT.c')).toContain('gen_rand32 (deps/jemalloc/test/src/SFMT.c)');
});
});
Loading