Skip to content
Open
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,7 @@ modules](https://go.dev/ref/mod) for dependency resolution.
You can run unit tests as follows:

```shell
go test -tags internal_testkit,internal_time_mock -short ./...
go test -tags internal_neo4j_go_driver_testkit,internal_neo4j_go_driver_time_mock -short ./...
```

### Integration and Benchmark Testing
Expand Down
9 changes: 7 additions & 2 deletions hooks/pre-commit
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,19 @@ if verlt $go_version "1.24.0"; then
exit 1
fi

tags="\
internal_neo4j_go_driver_testkit,\
internal_neo4j_go_driver_time_mock\
"

echo "# pre-commit hook"
printf '%-15s' "## staticcheck "
cd "$(mktemp -d)" && go install honnef.co/go/tools/cmd/staticcheck@"${staticcheck_version}" && cd - > /dev/null
"${GOBIN:-$(go env GOPATH)/bin}"/staticcheck -tags internal_testkit,internal_time_mock ./...
"${GOBIN:-$(go env GOPATH)/bin}"/staticcheck -tags "$tags" ./...
echo "✅"

printf '%-15s' "## go vet "
go vet -tags internal_testkit,internal_time_mock ./...
go vet -tags "$tags" ./...
echo "✅"

printf '%-15s' "## go test "
Expand Down
2 changes: 1 addition & 1 deletion neo4j/driver_testkit.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//go:build internal_testkit && internal_time_mock
//go:build internal_neo4j_go_driver_testkit && internal_neo4j_go_driver_time_mock

/*
* Copyright (c) "Neo4j"
Expand Down
2 changes: 1 addition & 1 deletion neo4j/internal/pool/pool_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//go:build internal_time_mock
//go:build internal_neo4j_go_driver_time_mock

/*
* Copyright (c) "Neo4j"
Expand Down
2 changes: 1 addition & 1 deletion neo4j/internal/retry/state_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//go:build internal_time_mock
//go:build internal_neo4j_go_driver_time_mock

/*
* Copyright (c) "Neo4j"
Expand Down
2 changes: 1 addition & 1 deletion neo4j/internal/router/router_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//go:build internal_time_mock
//go:build internal_neo4j_go_driver_time_mock

/*
* Copyright (c) "Neo4j"
Expand Down
2 changes: 1 addition & 1 deletion neo4j/internal/router/router_testkit.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//go:build internal_testkit
//go:build internal_neo4j_go_driver_testkit

/*
* Copyright (c) "Neo4j"
Expand Down
2 changes: 1 addition & 1 deletion neo4j/internal/time/time.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//go:build !internal_time_mock
//go:build !internal_neo4j_go_driver_time_mock

/*
* Copyright (c) "Neo4j"
Expand Down
2 changes: 1 addition & 1 deletion neo4j/internal/time/time_mockable.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//go:build internal_time_mock
//go:build internal_neo4j_go_driver_time_mock

/*
* Copyright (c) "Neo4j"
Expand Down
170 changes: 51 additions & 119 deletions testkit-backend/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ type backend struct {
clientCertificateProviders map[string]auth.ClientCertificateProvider
resolvedClientCertificates map[string]auth.ClientCertificate
closed bool
extrasData map[string]any
}

// To implement transactional functions a bit of extra state is needed on the
Expand Down Expand Up @@ -164,6 +165,7 @@ func newBackend(rd *bufio.Reader, wr io.Writer) *backend {
clientCertificateProviders: make(map[string]auth.ClientCertificateProvider),
resolvedClientCertificates: make(map[string]auth.ClientCertificate),
closed: false,
extrasData: newBackendExtraData(),
}
}

Expand Down Expand Up @@ -661,6 +663,13 @@ func (b *backend) handleRequest(req map[string]any) {
if data["connectionTimeoutMs"] != nil {
c.SocketConnectTimeout = time.Millisecond * time.Duration(asInt64(data["connectionTimeoutMs"].(json.Number)))
}
for _, configurer := range extrasDriverConfigurers {
err = configurer(b, data, c)
if err != nil {
b.writeError(err)
return
}
}
if data["notificationsMinSeverity"] != nil {
minSeverity, err := mapNotificationMinSeverityLevel(data["notificationsMinSeverity"].(string))
if err != nil {
Expand Down Expand Up @@ -708,6 +717,13 @@ func (b *backend) handleRequest(req map[string]any) {
if data["domainNameResolverRegistered"] != nil && data["domainNameResolverRegistered"].(bool) {
neo4j.RegisterDnsResolver(driver, b.dnsResolverFunction())
}
for _, handler := range extrasNewDriverHandlers {
err = handler(b, data, driver)
if err != nil {
b.writeError(err)
return
}
}

idKey := b.nextId()
b.drivers[idKey] = driver
Expand Down Expand Up @@ -814,6 +830,13 @@ func (b *backend) handleRequest(req map[string]any) {
}
config.Auth = &token
}
for _, configurer := range extrasExecuteQueryConfigurers {
err := configurer(b, data, config)
if err != nil {
b.writeError(err)
return
}
}
})
}

Expand Down Expand Up @@ -904,6 +927,13 @@ func (b *backend) handleRequest(req map[string]any) {
}
sessionConfig.Auth = &authToken
}
for _, configurer := range extrasSessionConfigurers {
err = configurer(b, data, &sessionConfig)
if err != nil {
b.writeError(err)
return
}
}
session := driver.NewSession(ctx, sessionConfig)
idKey := b.nextId()
b.sessionStates[idKey] = &sessionState{session: session}
Expand Down Expand Up @@ -1333,88 +1363,7 @@ func (b *backend) handleRequest(req map[string]any) {

case "GetFeatures":
b.writeResponse("FeatureList", map[string]any{
"features": []string{
// === FUNCTIONAL FEATURES ===
"Feature:API:BookmarkManager",
"Feature:API:ConnectionAcquisitionTimeout",
"Feature:API:Driver.ExecuteQuery",
"Feature:API:Driver.ExecuteQuery:WithAuth",
"Feature:API:Driver:GetServerInfo",
"Feature:API:Driver.IsEncrypted",
"Feature:API:Driver:MaxConnectionLifetime",
"Feature:API:Driver:NotificationsConfig",
"Feature:API:Driver.VerifyAuthentication",
"Feature:API:Driver.VerifyConnectivity",
//"Feature:API:Driver.SupportsSessionAuth",
"Feature:API:Liveness.Check",
"Feature:API:Result.List",
"Feature:API:Result.Peek",
//"Feature:API:Result.Single",
//"Feature:API:Result.SingleOptional",
"Feature:API:RetryableExceptions",
"Feature:API:Session:AuthConfig",
"Feature:API:Session:NotificationsConfig",
"Feature:API:SSLClientCertificate",
//"Feature:API:SSLConfig",
//"Feature:API:SSLSchemes",
"Feature:API:Summary:GqlStatusObjects",
"Feature:API:Type.Spatial",
"Feature:API:Type.Temporal",
"Feature:API:Type.Vector",
"Feature:API:Type.UnsupportedType",
"Feature:Auth:Bearer",
"Feature:Auth:Custom",
"Feature:Auth:Kerberos",
"Feature:Auth:Managed",
"Feature:Bolt:3.0",
"Feature:Bolt:4.2",
"Feature:Bolt:4.3",
"Feature:Bolt:4.4",
"Feature:Bolt:5.0",
"Feature:Bolt:5.1",
"Feature:Bolt:5.2",
"Feature:Bolt:5.3",
"Feature:Bolt:5.4",
"Feature:Bolt:5.5",
"Feature:Bolt:5.6",
"Feature:Bolt:5.7",
"Feature:Bolt:5.8",
"Feature:Bolt:6.0",
"Feature:Bolt:Patch:UTC",
"Feature:Bolt:HandshakeManifestV1",
"Feature:Impersonation",
//"Feature:TLS:1.1",
"Feature:TLS:1.2",
"Feature:TLS:1.3",

// === OPTIMIZATIONS ===
"AuthorizationExpiredTreatment",
"Optimization:AuthPipelining",
"Optimization:ConnectionReuse",
"Optimization:EagerTransactionBegin",
"Optimization:ExecuteQueryPipelining",
"Optimization:HomeDatabaseCache",
"Optimization:HomeDbCacheBasicPrincipalIsImpersonatedUser",
"Optimization:ImplicitDefaultArguments",
"Optimization:MinimalBookmarksSet",
"Optimization:MinimalResets",
//"Optimization:MinimalVerifyAuthentication",
"Optimization:PullPipelining",
//"Optimization:ResultListFetchAll",

// === IMPLEMENTATION DETAILS ===
"Detail:ClosedDriverIsEncrypted",
"Detail:DefaultSecurityConfigValueEquality",
//"Detail:NumberIsNumber",

// === CONFIGURATION HINTS (BOLT 4.3+) ===
"ConfHint:connection.recv_timeout_seconds",

// === BACKEND FEATURES FOR TESTING ===
"Backend:MockTime",
"Backend:RTFetch",
"Backend:RTForceUpdate",
},
"features": features,
})

case "StartTest":
Expand All @@ -1440,7 +1389,11 @@ func (b *backend) handleRequest(req map[string]any) {
b.writeResponse("RunTest", nil)

default:
b.writeError(errors.New("Unknown request: " + name))
if extraHandler, ok := extrasRequestHandlers[name]; ok {
extraHandler(b, data)
} else {
b.writeError(errors.New("Unknown request: " + name))
}
}
}

Expand Down Expand Up @@ -1522,7 +1475,7 @@ func (b *backend) writeRecord(result neo4j.Result, record *neo4j.Record, expectR
}

func mustSkip(testName string) (string, bool) {
skippedTests := testSkips()
skippedTests := testSkips
for testPattern, exclusionReason := range skippedTests {
if matches(testPattern, testName) {
return exclusionReason, true
Expand All @@ -1533,7 +1486,10 @@ func mustSkip(testName string) (string, bool) {

func mustSkipSubTest(testName string, arguments map[string]any) (string, bool) {
if strings.Contains(testName, "test_should_echo_all_timezone_ids") {
return mustSkipTimeZoneSubTest(arguments)
return mustSkipTimeZoneEchoSubTest(arguments)
}
if strings.Contains(testName, "test_date_time_cypher_created_tz_id") {
return mustSkipTimeZoneCypherSubTest(arguments)
}
return "", false
}
Expand Down Expand Up @@ -1766,40 +1722,7 @@ func firstRecordInvalidValue(record *db.Record) *neo4j.InvalidValue {
return nil
}

// you can use '*' as wildcards anywhere in the qualified test name (useful to exclude a whole class e.g.)
func testSkips() map[string]string {
return map[string]string{
// Won't fix - accepted/idiomatic behavioral differences
"stub.iteration.test_result_scope.TestResultScope.*": "Won't fix - Results are always valid but don't return records when out of scope",
"stub.connectivity_check.test_get_server_info.TestGetServerInfo.test_routing_fail_when_no_reader_are_available": "Won't fix - Go driver retries routing table when no readers are available",
"stub.connectivity_check.test_verify_connectivity.TestVerifyConnectivity.test_routing_fail_when_no_reader_are_available": "Won't fix - Go driver retries routing table when no readers are available",
"stub.driver_parameters.test_connection_acquisition_timeout_ms.TestConnectionAcquisitionTimeoutMs.test_does_not_encompass_router_*": "Won't fix - ConnectionAcquisitionTimeout spans the whole process including db resolution, RT updates, connection acquisition from the pool, and creation of new connections.",
"stub.driver_parameters.test_connection_acquisition_timeout_ms.TestConnectionAcquisitionTimeoutMs.test_router_handshake_has_own_timeout_*": "Won't fix - ConnectionAcquisitionTimeout spans the whole process including db resolution, RT updates, connection acquisition from the pool, and creation of new connections.",
"stub.routing.test_routing_v*.RoutingV*.test_should_successfully_check_if_support_for_multi_db_is_available": "Won't fix - driver.SupportsMultiDb() is not implemented",
"stub.routing.test_no_routing_v*.NoRoutingV*.test_should_check_multi_db_support": "Won't fix - driver.SupportsMultiDb() is not implemented",
"stub.routing.test_routing_v3.RoutingV3.test_should_fail_discovery_when_router_fails_with_procedure_not_found_code": "Won't fix - only Bolt 3 affected (not officially supported by this driver) + this is only a difference in how errors are surfaced",
"stub.routing.test_routing_v3.RoutingV3.test_should_fail_when_writing_on_unexpectedly_interrupting_writer_on_pull_using_tx_run": "Won't fix - only Bolt 3 affected (not officially supported by this driver): broken servers are not removed from routing table",
"stub.routing.test_routing_v3.RoutingV3.test_should_fail_when_writing_on_unexpectedly_interrupting_writer_on_run_using_tx_run": "Won't fix - only Bolt 3 affected (not officially supported by this driver): broken servers are not removed from routing table",
"stub.routing.test_routing_v3.RoutingV3.test_should_fail_when_writing_on_unexpectedly_interrupting_writer_using_tx_run": "Won't fix - only Bolt 3 affected (not officially supported by this driver): broken servers are not removed from routing table",

// To fix/to decide whether to fix
"stub.routing.*.*.test_should_successfully_acquire_rt_when_router_ip_changes": "Backend lacks custom DNS resolution and Go driver RT discovery differs.",
"stub.routing.test_routing_v*.RoutingV*.test_should_revert_to_initial_router_if_known_router_throws_protocol_errors": "Driver always uses configured URL first and custom resolver only if that fails",
"stub.routing.test_routing_v*.RoutingV*.test_should_request_rt_from_all_initial_routers_until_successful_on_authorization_expired": "Driver always uses configured URL first and custom resolver only if that fails",
"stub.routing.test_routing_v*.RoutingV*test_should_request_rt_from_all_initial_routers_until_successful_on_unknown_failure": "Driver always uses configured URL first and custom resolver only if that fails",
"stub.routing.test_routing_v*.RoutingV*.test_should_read_successfully_from_reachable_db_after_trying_unreachable_db": "Driver retries to fetch a routing table up to 100 times if it's empty",
"stub.routing.test_routing_v*.RoutingV*.test_should_write_successfully_after_leader_switch_using_tx_run": "Driver retries to fetch a routing table up to 100 times if it's empty",
"stub.routing.test_routing_v*.RoutingV*.test_should_fail_when_writing_without_writers_using_session_run": "Driver retries to fetch a routing table up to 100 times if it's empty",
"stub.routing.test_routing_v*.RoutingV*.test_should_accept_routing_table_without_writers_and_then_rediscover": "Driver retries to fetch a routing table up to 100 times if it's empty",
"stub.routing.test_routing_v*.RoutingV*.test_should_fail_on_routing_table_with_no_reader": "Driver retries to fetch a routing table up to 100 times if it's empty",
"stub.routing.test_routing_v*.RoutingV*.test_should_fail_discovery_when_router_fails_with_unknown_code": "Unify: other drivers have a list of fast failing errors during discover: on anything else, the driver will try the next router",
"stub.routing.test_routing_v*.RoutingV*.test_should_drop_connections_failing_liveness_check": "Liveness check error handling is not (yet) unified: https://github.com/neo-technology/drivers-adr/pull/83",
"stub.*.test_0_timeout": "Fixme: driver omits 0 as tx timeout value",
"stub.summary.test_summary.TestSummaryBasicInfo*.test_server_info": "pending unification: should the server address be pre or post DNS resolution?",
}
}

func mustSkipTimeZoneSubTest(arguments map[string]any) (string, bool) {
func mustSkipTimeZoneEchoSubTest(arguments map[string]any) (string, bool) {
rawDateTime := arguments["dt"].(map[string]any)
dateTimeData := rawDateTime["data"].(map[string]any)
timeZoneName := dateTimeData["timezone_id"].(string)
Expand All @@ -1826,6 +1749,15 @@ func mustSkipTimeZoneSubTest(arguments map[string]any) (string, bool) {
return "", false
}

func mustSkipTimeZoneCypherSubTest(arguments map[string]any) (string, bool) {
timeZoneName := arguments["tz_id"].(string)
_, err := time.LoadLocation(timeZoneName)
if err != nil {
return fmt.Sprintf("time zone not supported: %s", err), true
}
return "", false
}

// some TestKit tests send large integer values which require to configure
// the JSON deserializer to use json.Number instead of float64 (lossy conversions
// would happen otherwise)
Expand Down
24 changes: 24 additions & 0 deletions testkit-backend/build_tags.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/usr/bin/env bash

set -Eeuo pipefail

verlte() {
[ "$1" = "$(echo -e "$1\n$2" | sort -V | head -n1)" ]
}

verlt() {
[ "$1" = "$2" ] && return 1 || verlte "$1" "$2"
}

version="${1:-''}"

if [ -z "$version" ]; then
# choose a version bigger than any version checked below
# => next major version, at which point we can clean-up this script as it only needs to support the current major
version="7.0.0"
elif [ "$(echo "$version" | cut -d "." -f 1)" != "6" ]; then
echo "Script only works for 6.x" >&2
exit 1
fi

echo $tags
Loading