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
153 changes: 153 additions & 0 deletions .github/workflows/check-schema-changes.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
name: Check C2PA schema changes

on:
schedule:
# Check for schema changes every 6 hours (offset from SDK update check)
- cron: "30 */6 * * *"
workflow_dispatch:

jobs:
check-schema:
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable

- name: Get current and previous c2pa-rs tags
id: get_tags
run: |
TAGS=$(curl -s "https://api.github.com/repos/contentauth/c2pa-rs/releases" | \
jq -r '.[].tag_name | select(test("^c2pa-v"))' | \
head -2)

CURRENT_TAG=$(echo "$TAGS" | head -1)
PREVIOUS_TAG=$(echo "$TAGS" | tail -1)

if [ -z "$CURRENT_TAG" ] || [ -z "$PREVIOUS_TAG" ]; then
echo "Could not find two consecutive c2pa release tags"
exit 1
fi

if [ "$CURRENT_TAG" = "$PREVIOUS_TAG" ]; then
echo "Only one tag found, nothing to compare"
exit 1
fi

echo "current_tag=$CURRENT_TAG" >> $GITHUB_OUTPUT
echo "previous_tag=$PREVIOUS_TAG" >> $GITHUB_OUTPUT
echo "Current tag: $CURRENT_TAG"
echo "Previous tag: $PREVIOUS_TAG"

- name: Clone c2pa-rs
run: |
git clone https://github.com/contentauth/c2pa-rs.git /tmp/c2pa-rs
cd /tmp/c2pa-rs
git fetch --tags

- name: Generate schemas for both tags
run: |
for TAG_VAR in current previous; do
if [ "$TAG_VAR" = "current" ]; then
TAG="${{ steps.get_tags.outputs.current_tag }}"
else
TAG="${{ steps.get_tags.outputs.previous_tag }}"
fi

cd /tmp/c2pa-rs
git checkout "$TAG"
cargo run -p export_schema --features c2pa/rust_native_crypto 2>/dev/null || \
cargo run -p export_schema 2>/dev/null
mkdir -p "/tmp/schemas-$TAG_VAR"
cp target/schema/*.schema.json "/tmp/schemas-$TAG_VAR/"
done

- name: Compare schemas
id: compare
run: |
CHANGES=""
HAS_CHANGES=false

for schema in /tmp/schemas-current/*.schema.json; do
NAME=$(basename "$schema")
PREV="/tmp/schemas-previous/$NAME"

if [ ! -f "$PREV" ]; then
CHANGES="${CHANGES}- **${NAME}**: New schema (not present in previous tag)\n"
HAS_CHANGES=true
continue
fi

DIFF=$(diff \
<(python3 -c "import json,sys; json.dump(json.load(open('$PREV')),sys.stdout,sort_keys=True,indent=2)") \
<(python3 -c "import json,sys; json.dump(json.load(open('$schema')),sys.stdout,sort_keys=True,indent=2)") \
|| true)

if [ -n "$DIFF" ]; then
ADDED=$(echo "$DIFF" | grep -c '^>' || true)
REMOVED=$(echo "$DIFF" | grep -c '^<' || true)
CHANGES="${CHANGES}- **${NAME}**: ${ADDED} additions, ${REMOVED} removals\n"
HAS_CHANGES=true
fi
done

for schema in /tmp/schemas-previous/*.schema.json; do
NAME=$(basename "$schema")
if [ ! -f "/tmp/schemas-current/$NAME" ]; then
CHANGES="${CHANGES}- **${NAME}**: Schema removed\n"
HAS_CHANGES=true
fi
done

echo "has_changes=$HAS_CHANGES" >> $GITHUB_OUTPUT

if [ "$HAS_CHANGES" = true ]; then
echo "changes<<EOF" >> $GITHUB_OUTPUT
echo -e "$CHANGES" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
fi

- name: Ensure schema-change label exists
if: steps.compare.outputs.has_changes == 'true'
run: |
gh label create "schema-change" \
--description "Upstream c2pa-rs JSON schema has changed" \
--color "D93F0B" \
2>/dev/null || true
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Check for existing open issue
if: steps.compare.outputs.has_changes == 'true'
id: existing_issue
run: |
EXISTING=$(gh issue list \
--label "schema-change" \
--state open \
--json number \
--jq '.[0].number // empty')

echo "number=$EXISTING" >> $GITHUB_OUTPUT

if [ -n "$EXISTING" ]; then
echo "Open schema change issue already exists: #$EXISTING"
fi
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: File issue for schema changes
if: steps.compare.outputs.has_changes == 'true' && steps.existing_issue.outputs.number == ''
run: |
gh issue create \
--title "C2PA schema changes detected between ${{ steps.get_tags.outputs.previous_tag }} and ${{ steps.get_tags.outputs.current_tag }}" \
--label "schema-change" \
--body "Review the upstream schema changes and update \`C2PASettingsDefinition.kt\` and any other typed data classes to match.

${{ steps.compare.outputs.changes }}"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
2 changes: 1 addition & 1 deletion library/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ android {

// Specify ABIs to use prebuilt .so files
ndk {
abiFilters.add("x86_64")
abiFilters.add("arm64-v8a")
abiFilters.add("armeabi-v7a")
abiFilters.add("x86")
abiFilters.add("x86_64")
}
}

Expand Down
2 changes: 1 addition & 1 deletion library/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# C2PA Native Library Version
# Update this to use a different release from https://github.com/contentauth/c2pa-rs/releases
c2paVersion=v0.75.8
c2paVersion=v0.75.19
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*
/*
This file is licensed to you under the Apache License, Version 2.0
(http://www.apache.org/licenses/LICENSE-2.0) or the MIT license
(http://opensource.org/licenses/MIT), at your option.
Expand All @@ -9,6 +9,7 @@ ANY KIND, either express or implied. See the LICENSE-MIT and LICENSE-APACHE
files for the specific language governing permissions and limitations under
each license.
*/

package org.contentauth.c2pa

import android.content.Context
Expand Down Expand Up @@ -85,4 +86,52 @@ class AndroidBuilderTests : BuilderTests() {
val result = testJsonRoundTrip()
assertTrue(result.success, "JSON Round-trip test failed: ${result.message}")
}

@Test
fun runTestBuilderFromContextWithSettings() = runBlocking {
val result = testBuilderFromContextWithSettings()
assertTrue(result.success, "Builder from Context with Settings test failed: ${result.message}")
}

@Test
fun runTestBuilderFromJsonWithSettings() = runBlocking {
val result = testBuilderFromJsonWithSettings()
assertTrue(result.success, "Builder fromJson with Settings test failed: ${result.message}")
}

@Test
fun runTestBuilderWithArchive() = runBlocking {
val result = testBuilderWithArchive()
assertTrue(result.success, "Builder withArchive test failed: ${result.message}")
}

@Test
fun runTestReaderFromContext() = runBlocking {
val result = testReaderFromContext()
assertTrue(result.success, "Reader fromContext test failed: ${result.message}")
}

@Test
fun runTestBuilderSetIntent() = runBlocking {
val result = testBuilderSetIntent()
assertTrue(result.success, "Builder Set Intent test failed: ${result.message}")
}

@Test
fun runTestBuilderAddAction() = runBlocking {
val result = testBuilderAddAction()
assertTrue(result.success, "Builder Add Action test failed: ${result.message}")
}

@Test
fun runTestSettingsSetValue() = runBlocking {
val result = testSettingsSetValue()
assertTrue(result.success, "C2PASettings setValue test failed: ${result.message}")
}

@Test
fun runTestBuilderIntentEditAndUpdate() = runBlocking {
val result = testBuilderIntentEditAndUpdate()
assertTrue(result.success, "Builder Intent Edit and Update test failed: ${result.message}")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/*
This file is licensed to you under the Apache License, Version 2.0
(http://www.apache.org/licenses/LICENSE-2.0) or the MIT license
(http://opensource.org/licenses/MIT), at your option.

Unless required by applicable law or agreed to in writing, this software is
distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS OF
ANY KIND, either express or implied. See the LICENSE-MIT and LICENSE-APACHE
files for the specific language governing permissions and limitations under
each license.
*/
package org.contentauth.c2pa

import android.content.Context
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import kotlinx.coroutines.runBlocking
import org.contentauth.c2pa.test.shared.SettingsDefinitionTests
import org.junit.Test
import org.junit.runner.RunWith
import java.io.File
import kotlin.test.assertTrue

/** Android instrumented tests for C2PASettingsDefinition. */
@RunWith(AndroidJUnit4::class)
class AndroidSettingsDefinitionTests : SettingsDefinitionTests() {

private val targetContext = InstrumentationRegistry.getInstrumentation().targetContext

override fun getContext(): Context = targetContext

override fun loadResourceAsBytes(resourceName: String): ByteArray =
ResourceTestHelper.loadResourceAsBytes(resourceName)

override fun loadResourceAsString(resourceName: String): String =
ResourceTestHelper.loadResourceAsString(resourceName)

override fun copyResourceToFile(resourceName: String, fileName: String): File =
ResourceTestHelper.copyResourceToFile(targetContext, resourceName, fileName)

@Test
fun runTestRoundTrip() = runBlocking {
val result = testRoundTrip()
assertTrue(result.success, "Round Trip test failed: ${result.message}")
}

@Test
fun runTestFromJson() = runBlocking {
val result = testFromJson()
assertTrue(result.success, "fromJson test failed: ${result.message}")
}

@Test
fun runTestSettingsIntent() = runBlocking {
val result = testSettingsIntent()
assertTrue(result.success, "Settings Intent test failed: ${result.message}")
}

@Test
fun runTestToJson() = runBlocking {
val result = testToJson()
assertTrue(result.success, "toJson test failed: ${result.message}")
}

@Test
fun runTestSignerSettings() = runBlocking {
val result = testSignerSettings()
assertTrue(result.success, "Signer Settings test failed: ${result.message}")
}

@Test
fun runTestCawgSigner() = runBlocking {
val result = testCawgSigner()
assertTrue(result.success, "CAWG Signer test failed: ${result.message}")
}

@Test
fun runTestIgnoreUnknownKeys() = runBlocking {
val result = testIgnoreUnknownKeys()
assertTrue(result.success, "Ignore Unknown Keys test failed: ${result.message}")
}

@Test
fun runTestBuilderSettings() = runBlocking {
val result = testBuilderSettings()
assertTrue(result.success, "Builder Settings test failed: ${result.message}")
}

@Test
fun runTestFromDefinition() = runBlocking {
val result = testFromDefinition()
assertTrue(result.success, "fromDefinition test failed: ${result.message}")
}

@Test
fun runTestUpdateFrom() = runBlocking {
val result = testUpdateFrom()
assertTrue(result.success, "updateFrom test failed: ${result.message}")
}

@Test
fun runTestPrettyJson() = runBlocking {
val result = testPrettyJson()
assertTrue(result.success, "Pretty JSON test failed: ${result.message}")
}

@Test
fun runTestEnumSerialization() = runBlocking {
val result = testEnumSerialization()
assertTrue(result.success, "Enum Serialization test failed: ${result.message}")
}

@Test
fun runTestActionTemplates() = runBlocking {
val result = testActionTemplates()
assertTrue(result.success, "Action Templates test failed: ${result.message}")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,21 @@ class AndroidStreamTests : StreamTests() {
assertTrue(result.success, "Custom Stream Callbacks test failed: ${result.message}")
}

@Test
fun runTestCallbackStreamFactories() = runBlocking {
val result = testCallbackStreamFactories()
assertTrue(result.success, "Callback Stream Factories test failed: ${result.message}")
}

@Test
fun runTestByteArrayStreamBufferGrowth() = runBlocking {
val result = testByteArrayStreamBufferGrowth()
assertTrue(
result.success,
"ByteArrayStream Buffer Growth test failed: ${result.message}",
)
}

@Test
fun runTestFileOperationsWithDataDirectory() = runBlocking {
val result = testFileOperationsWithDataDirectory()
Expand Down
Loading
Loading