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
28 changes: 25 additions & 3 deletions client/src/cbltest/api/edgeserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from cbltest.assertions import _assert_not_null
from cbltest.httplog import get_next_writer
from cbltest.jsonhelper import _get_typed_required
from cbltest.logging import cbl_warning
from cbltest.version import VERSION


Expand All @@ -40,7 +41,22 @@ def parse(self, input: str) -> tuple[str, int]:
if first_lparen == -1 or first_semicol == -1:
return ("unknown", 0)

return input[0:first_lparen], int(input[first_lparen + 1 : first_semicol])
version = input[0:first_lparen].strip()
if not version:
cbl_warning(
f"Could not extract version from Edge Server version string: '{input}'"
)
version = "unknown"

try:
build = int(input[first_lparen + 1 : first_semicol])
except ValueError:
cbl_warning(
f"Could not parse build number from Edge Server version string: '{input}'"
)
build = 0

return (version, build)


class BulkDocOperation(JSONSerializable):
Expand Down Expand Up @@ -229,8 +245,14 @@ async def get_version(self) -> CouchbaseVersion:
assert isinstance(resp, dict)
resp_dict = cast(dict, resp)
raw_version = _get_typed_required(resp_dict, "version", str)
assert "/" in raw_version
return EdgeServerVersion(raw_version.split("/")[1])
if "/" in raw_version:
version_part = raw_version.rsplit("/", 1)[1]
else:
cbl_warning(
f"Unexpected Edge Server version format (no '/' separator): '{raw_version}'"
)
version_part = raw_version
return EdgeServerVersion(version_part)

async def get_all_documents(
self,
Expand Down
25 changes: 22 additions & 3 deletions client/src/cbltest/api/syncgateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -498,7 +498,20 @@ def parse(self, input: str) -> tuple[str, int]:
if first_lparen == -1 or first_semicol == -1:
return ("unknown", 0)

return input[0:first_lparen], int(input[first_lparen + 1 : first_semicol])
version = input[0:first_lparen].strip()
if not version:
cbl_warning(f"Could not extract version from SGW version string: '{input}'")
version = "unknown"

try:
build = int(input[first_lparen + 1 : first_semicol])
except ValueError:
cbl_warning(
f"Could not parse build number from SGW version string: '{input}'"
)
build = 0

return (version, build)


class DatabaseStatusResponse:
Expand Down Expand Up @@ -644,8 +657,14 @@ async def get_version(self) -> CouchbaseVersion:
assert isinstance(resp, dict)
resp_dict = cast(dict, resp)
raw_version = _get_typed_required(resp_dict, "version", str)
assert "/" in raw_version
return SyncGatewayVersion(raw_version.split("/")[1])
if "/" in raw_version:
version_part = raw_version.rsplit("/", 1)[1]
else:
cbl_warning(
f"Unexpected SGW version format (no '/' separator): '{raw_version}'"
)
version_part = raw_version
return SyncGatewayVersion(version_part)

def tls_cert(self) -> str | None:
if not self.secure:
Expand Down
55 changes: 39 additions & 16 deletions client/src/cbltest/greenboarduploader.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from couchbase.cluster import Cluster
from couchbase.options import ClusterOptions

from cbltest.api.syncgateway import CouchbaseVersion
from cbltest.logging import cbl_warning


Expand Down Expand Up @@ -54,35 +55,57 @@ def has_sgw_marker(self) -> bool:
"""
return self.__has_sgw_marker

def upload(self, platform: str, os_name: str, version: str, sgw_version: str):
def upload(
self,
platform: str,
os_name: str,
version: str,
sgw_version: CouchbaseVersion | None,
):
"""
Uploads the results using the specified platform and version. The reason that they
are specified here is because they are probably unknown at the time that this object
is created as they need to be retrieved from the test server.

:param platform: The platform name (e.g. couchbase-lite-net) as specified by the test server
:param version: The version string (e.g. 3.2.0-b0136, etc) as specified by the test server
:param sgw_version: The parsed SGW CouchbaseVersion object, or None if unavailable
"""
if self.__overall_fail:
cbl_warning("Overall result is failure, skipping upload...")
return

version_to_parse = sgw_version if platform == "sync-gateway" else version
version_components = version_to_parse.split("-")

parsed_version = "0.0.0"
parsed_build = 0

if len(version_components) > 0 and version_components[0]:
parsed_version = version_components[0]

if len(version_components) > 1:
try:
# Handles build numbers like 'b1234' or just '1234'
parsed_build = int(version_components[1].lstrip("b"))
except ValueError:
# If the part after '-' is not a number, build remains 0
cbl_warning(f"Could not parse build number from '{version_to_parse}'")
sgw_version_str = "n/a"

if sgw_version is not None:
sgw_ver = sgw_version.version
sgw_build = sgw_version.build_number
sgw_version_str = f"{sgw_ver}-{sgw_build}"

if platform == "sync-gateway" and sgw_version is not None:
# For SGW jobs, use the SGW version directly from the parsed object
# to avoid the fragile serialize-then-reparse pattern.
parsed_version = (
sgw_version.version
if sgw_version.version and sgw_version.version != "unknown"
else "0.0.0"
)
parsed_build = sgw_version.build_number
else:
version_components = version.split("-")

if len(version_components) > 0 and version_components[0]:
parsed_version = version_components[0]

if len(version_components) > 1:
try:
# Handles build numbers like 'b1234' or just '1234'
parsed_build = int(version_components[1].lstrip("b"))
except ValueError:
# If the part after '-' is not a number, build remains 0
cbl_warning(f"Could not parse build number from '{version}'")

auth = PasswordAuthenticator(self.__username, self.__password)
opts = ClusterOptions(auth)
Expand All @@ -98,7 +121,7 @@ def upload(self, platform: str, os_name: str, version: str, sgw_version: str):
{
"build": parsed_build,
"version": parsed_version,
"sgwVersion": sgw_version,
"sgwVersion": sgw_version_str,
"failCount": self.__fail_count,
"passCount": self.__pass_count,
"platform": platform,
Expand Down
8 changes: 3 additions & 5 deletions client/src/cbltest/plugins/greenboard_fixture.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import pytest
import pytest_asyncio
from cbltest import CBLPyTest
from cbltest.api.syncgateway import CouchbaseVersion
from cbltest.greenboarduploader import GreenboardUploader
from cbltest.logging import cbl_info, cbl_warning

Expand Down Expand Up @@ -43,7 +44,7 @@ async def greenboard(cblpytest: CBLPyTest, pytestconfig: pytest.Config):
yield

try:
sgw_version: str = "n/a"
sgw_version: CouchbaseVersion | None = None
test_platform: str = "sync-gateway"
os_name: str = "n/a"
library_version: str = "n/a"
Expand All @@ -58,10 +59,7 @@ async def greenboard(cblpytest: CBLPyTest, pytestconfig: pytest.Config):
if "systemName" in test_server_info.device:
os_name = test_server_info.device["systemName"]
if len(cblpytest.sync_gateways) > 0:
sgw_version_parts = await cblpytest.sync_gateways[0].get_version()
sgw_version = (
f"{sgw_version_parts.version}-{sgw_version_parts.build_number}"
)
sgw_version = await cblpytest.sync_gateways[0].get_version()
uploader.upload(test_platform, os_name, library_version, sgw_version)
except Exception as e:
cbl_warning(f"Failed to upload results to Greenboard: {e}")
Expand Down
85 changes: 85 additions & 0 deletions client/tests/test_version_parsing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
"""Unit tests for CouchbaseVersion parsing (SyncGatewayVersion / EdgeServerVersion)
and GreenboardUploader version handling."""

from cbltest.api.edgeserver import EdgeServerVersion
from cbltest.api.syncgateway import SyncGatewayVersion


class TestSyncGatewayVersionParse:
"""Tests for SyncGatewayVersion.parse()"""

def test_standard_3x_format(self):
v = SyncGatewayVersion("3.3.3(271;abc123)")
assert v.version == "3.3.3"
assert v.build_number == 271

def test_standard_4x_format(self):
v = SyncGatewayVersion("4.0.0(350;def456)")
assert v.version == "4.0.0"
assert v.build_number == 350

def test_missing_parentheses(self):
v = SyncGatewayVersion("4.0.0")
assert v.version == "unknown"
assert v.build_number == 0

def test_missing_semicolon(self):
v = SyncGatewayVersion("4.0.0(271)")
assert v.version == "unknown"
assert v.build_number == 0

def test_empty_version_before_lparen(self):
"""Previously returned ("", 271) — now returns ("unknown", 271)."""
v = SyncGatewayVersion("(271;abc)")
assert v.version == "unknown"
assert v.build_number == 271

def test_non_numeric_build(self):
"""Non-numeric build between ( and ; should not crash."""
v = SyncGatewayVersion("4.0.0(abc;def)")
assert v.version == "4.0.0"
assert v.build_number == 0

def test_version_with_spaces(self):
v = SyncGatewayVersion("4.0.0 (271;commit)")
assert v.version == "4.0.0"
assert v.build_number == 271

def test_empty_input(self):
v = SyncGatewayVersion("")
assert v.version == "unknown"
assert v.build_number == 0

def test_raw_preserved(self):
v = SyncGatewayVersion("3.3.3(271;abc)")
assert v.raw == "3.3.3(271;abc)"

def test_enterprise_edition_suffix(self):
"""Handle version strings like '4.0.0(271;commit) EE'."""
v = SyncGatewayVersion("4.0.0(271;commit) EE")
assert v.version == "4.0.0"
assert v.build_number == 271


class TestEdgeServerVersionParse:
"""Tests for EdgeServerVersion.parse() — same logic as SyncGatewayVersion."""

def test_standard_format(self):
v = EdgeServerVersion("1.2.0(100;abc)")
assert v.version == "1.2.0"
assert v.build_number == 100

def test_empty_version_before_lparen(self):
v = EdgeServerVersion("(100;abc)")
assert v.version == "unknown"
assert v.build_number == 100

def test_non_numeric_build(self):
v = EdgeServerVersion("1.0.0(xyz;abc)")
assert v.version == "1.0.0"
assert v.build_number == 0

def test_no_parentheses(self):
v = EdgeServerVersion("1.0.0")
assert v.version == "unknown"
assert v.build_number == 0
30 changes: 25 additions & 5 deletions jenkins/pipelines/QE/sgw/Jenkinsfile
Original file line number Diff line number Diff line change
@@ -1,16 +1,36 @@
def resolveProgetVersion(String product, String version, String label) {
def url = "http://proget.build.couchbase.com:8080/api/latest_release?product=${product}&version=${version}&prerelease=true"
echo "Resolving ${label}: ${url}"
def resolved
if (isUnix()) {
resolved = sh(script: "curl -sf '${url}' | jq -r .version", returnStdout: true).trim()
} else {
resolved = powershell(script: """
try { (Invoke-RestMethod '${url}').version }
catch { Write-Error "ProGet request failed for ${label}: \$_"; exit 1 }
""".stripIndent(), returnStdout: true).trim()
}
if (!resolved || resolved == 'null') { error "Could not resolve ${label} from '${version}' (url: ${url})" }
echo "Resolved ${label}: ${resolved}"
return resolved
}

pipeline {
agent none
parameters {
string(name: 'CBL_VERSION', defaultValue: '4.0.0', description: 'Couchbase Lite Version to use')
string(name: 'SGW_VERSION', defaultValue: '4.0.0', description: 'Sync Gateway Version to use')
string(name: 'CBL_VERSION', defaultValue: '3', description: 'Couchbase Lite Version to use')
string(name: 'SGW_VERSION', defaultValue: '4', description: 'Sync Gateway Version to use')
}
stages {
stage('Init') {
agent any
steps {
script {
if (params.CBL_VERSION == '') { error "CBL_VERSION is required" }
if (params.SGW_VERSION == '') { error "SGW_VERSION is required" }
currentBuild.displayName = "CBL:${params.CBL_VERSION} SGW:${params.SGW_VERSION} (#${currentBuild.number})"
env.CBL_VERSION = resolveProgetVersion("couchbase-lite-ios", params.CBL_VERSION, 'CBL_VERSION')
env.SGW_VERSION = resolveProgetVersion('sync-gateway', params.SGW_VERSION, 'SGW_VERSION')
currentBuild.displayName = "CBL:${env.CBL_VERSION} SGW:${env.SGW_VERSION} (#${currentBuild.number})"
}
}
}
Expand All @@ -19,7 +39,7 @@ pipeline {
build job: 'prebuild-test-server',
parameters: [
string(name: 'TS_PLATFORM', value: 'swift_ios'),
string(name: 'CBL_VERSION', value: params.CBL_VERSION),
string(name: 'CBL_VERSION', value: env.CBL_VERSION),
],
wait: true,
propagate: true
Expand All @@ -37,7 +57,7 @@ pipeline {
sh "security unlock-keychain -p '${KEYCHAIN_PASSWORD}' ~/Library/Keychains/login.keychain-db"
echo "=== Run SGW Tests"
timeout(time: 90, unit: 'MINUTES') {
sh "jenkins/pipelines/QE/sgw/test.sh ${params.CBL_VERSION} ${params.SGW_VERSION}"
sh "jenkins/pipelines/QE/sgw/test.sh ${env.CBL_VERSION} ${env.SGW_VERSION}"
}
echo "=== SGW Tests Complete"
}
Expand Down
Loading
Loading