Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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: 2 additions & 0 deletions FirebaseAI/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Unreleased
- [fixed] Fixed various links in the Live API doc comments not mapping correctly.
- [fixed] Fixed minor translation issue for nanosecond conversion when receiving
`LiveServerGoingAwayNotice`. (#15410)

# 12.4.0
- [feature] Added support for the URL context tool, which allows the model to access content
Expand Down
35 changes: 34 additions & 1 deletion FirebaseAI/Sources/Types/Internal/ProtoDuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,39 @@ extension ProtoDuration: Decodable {
}

self.seconds = secs
self.nanos = nanos
self.nanos = fractionalSecondsToNanoseconds(nanos, digits: nanoseconds.count)
}
}

/// Cached powers of 10 for quickly mapping fractional seconds.
private let pow10: [Int32] = [
1, 10, 100, 1000, 10000, 100_000,
1_000_000, 10_000_000, 100_000_000, 1_000_000_000,
]

/// Converts a fractional second representing a nanosecond to a valid nanosecond value.
///
/// It's expected that both parameters are positive and that `digits` fits within 9 digits.
///
/// ```swift
/// // 0.123456
/// XCTAssertEqual(
/// fractionalSecondsToNanoseconds(123456, 6),
/// 123456000
/// )
///
/// // 0.000123456
/// XCTAssertEqual(
/// fractionalSecondsToNanoseconds(123456, 9),
/// 123456
/// )
///
/// // 0.123456789
/// XCTAssertEqual(
/// fractionalSecondsToNanoseconds(123456789, 9),
/// 123456789
/// )
/// ```
private func fractionalSecondsToNanoseconds(_ value: Int32, digits: Int) -> Int32 {
return Int32(truncatingIfNeeded: value) &* pow10[9 - digits]
}
30 changes: 30 additions & 0 deletions FirebaseAI/Tests/Unit/TestUtilities/XCTUtil.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import XCTest

/// Asserts that a string contains another string.
///
/// ```swift
/// XCTAssertContains("my name is", "name")
/// ```
///
/// - Parameters:
/// - string: The source string that should contain the other.
/// - contains: The string that should be contained in the source string.
func XCTAssertContains(_ string: String, _ contains: String) {
if !string.contains(contains) {
XCTFail("(\"\(string)\") does not contain (\"\(contains)\")")
}
}
99 changes: 99 additions & 0 deletions FirebaseAI/Tests/Unit/Types/ProtoDurationTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import XCTest

@testable import FirebaseAI

final class ProtoDurationTests: XCTestCase {
let decoder = JSONDecoder()

private func decodeProtoDuration(_ jsonString: String) throws -> ProtoDuration {
let escapedString = "\"\(jsonString)\""
let jsonData = try XCTUnwrap(escapedString.data(using: .utf8))

return try decoder.decode(ProtoDuration.self, from: jsonData)
}

private func expectDecodeFailure(_ jsonString: String) throws -> DecodingError.Context? {
do {
let _ = try decodeProtoDuration(jsonString)
XCTFail("Expected decoding to fail")
return nil
} catch {
let decodingError = try XCTUnwrap(error as? DecodingError)
guard case let .dataCorrupted(dataCorrupted) = decodingError else {
XCTFail("Error was not a data corrupted error")
return nil
}

return dataCorrupted
}
}

func testDecodeProtoDuration_standardDuration() throws {
let duration = try decodeProtoDuration("120.000000123s")
XCTAssertEqual(duration.seconds, 120)
XCTAssertEqual(duration.nanos, 123)

XCTAssertEqual(duration.timeInterval, 120.000000123)
}

func testDecodeProtoDuration_withoutNanoseconds() throws {
let duration = try decodeProtoDuration("120s")
XCTAssertEqual(duration.seconds, 120)
XCTAssertEqual(duration.nanos, 0)

XCTAssertEqual(duration.timeInterval, 120)
}

func testDecodeProtoDuration_maxNanosecondDigits() throws {
let duration = try decodeProtoDuration("15.123456789s")
XCTAssertEqual(duration.seconds, 15)
XCTAssertEqual(duration.nanos, 123_456_789)

XCTAssertEqual(duration.timeInterval, 15.123456789)
}

func testDecodeProtoDuration_withMilliseconds() throws {
let duration = try decodeProtoDuration("15.123s")
XCTAssertEqual(duration.seconds, 15)
XCTAssertEqual(duration.nanos, 123_000_000)

XCTAssertEqual(duration.timeInterval, 15.123000000)
}

func testDecodeProtoDuration_invalidSeconds() throws {
guard let error = try expectDecodeFailure("invalid.123s") else { return }
XCTAssertContains(error.debugDescription, "Invalid proto duration seconds")
}

func testDecodeProtoDuration_invalidNanoseconds() throws {
guard let error = try expectDecodeFailure("123.invalid") else { return }
XCTAssertContains(error.debugDescription, "Invalid proto duration nanoseconds")
}

func testDecodeProtoDuration_tooManyDecimals() throws {
guard let error = try expectDecodeFailure("123.45.67") else { return }
XCTAssertContains(error.debugDescription, "Invalid proto duration string")
}

func testDecodeProtoDuration_withoutSuffix() throws {
let duration = try decodeProtoDuration("123.456")
XCTAssertEqual(duration.seconds, 123)
XCTAssertEqual(duration.nanos, 456_000_000)

XCTAssertEqual(duration.timeInterval, 123.456)
}
}
Loading