Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47a7e35fea | ||
|
|
8b485c0bad | ||
|
|
6c17c3f04b |
No files matched your search
@@ -7,30 +7,38 @@ on:
|
||||
|
||||
jobs:
|
||||
android:
|
||||
runs-on: windows-latest
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 40
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
github-server-url: https://gitea.twinpay.one
|
||||
github-server-url: https://gitea.turkservis.online
|
||||
- name: Set up Java 21
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: '21'
|
||||
- name: Set up Android SDK
|
||||
shell: powershell
|
||||
run: ./scripts/setup-android-ci.ps1
|
||||
shell: bash
|
||||
run: bash scripts/setup-android-ci.sh
|
||||
- name: Test and build exact commit
|
||||
shell: powershell
|
||||
shell: bash
|
||||
run: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
if ((git rev-parse HEAD).Trim() -ne $env:GITHUB_SHA) { throw 'Checkout SHA mismatch' }
|
||||
Get-CimInstance Win32_OperatingSystem | Select-Object TotalVisibleMemorySize, FreePhysicalMemory
|
||||
# A fresh runner compiles both variants from scratch. Keep their compiler
|
||||
# heaps separate in time; the previous shared 2 GiB heap exhausted itself.
|
||||
$buildArgs = @('--no-daemon', '--console=plain', '--max-workers=1', '-Pkotlin.daemon.jvmargs=-Xmx4096m')
|
||||
.\gradlew.bat testDebugUnitTest assembleDebug @buildArgs
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
.\gradlew.bat compileReleaseKotlin @buildArgs
|
||||
exit $LASTEXITCODE
|
||||
set -euo pipefail
|
||||
test "$(git rev-parse HEAD)" = "$GITHUB_SHA"
|
||||
uname -a
|
||||
free -m
|
||||
# One compiler process inside the server's bounded, serial CI runner.
|
||||
build_args=(--no-daemon --console=plain --max-workers=1
|
||||
'-Dorg.gradle.jvmargs=-Xmx2560m -Dfile.encoding=UTF-8'
|
||||
'-Pkotlin.compiler.execution.strategy=in-process')
|
||||
bash gradlew testDebugUnitTest assembleDebug "${build_args[@]}"
|
||||
bash gradlew compileReleaseKotlin "${build_args[@]}"
|
||||
- name: Keep exact-commit APK and test reports
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: aiturk-android-${{ github.sha }}
|
||||
if-no-files-found: error
|
||||
path: |
|
||||
app/build/outputs/apk/debug/app-debug.apk
|
||||
app/build/reports/tests/testDebugUnitTest
|
||||
@@ -0,0 +1,115 @@
|
||||
package com.m57.hermescontrol.ui.cron
|
||||
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.test.hasContentDescription
|
||||
import androidx.compose.ui.test.junit4.createComposeRule
|
||||
import androidx.compose.ui.test.onAllNodesWithText
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.compose.ui.test.performScrollTo
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.ViewModelStore
|
||||
import androidx.lifecycle.ViewModelStoreOwner
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import com.m57.hermescontrol.BuildConfig
|
||||
import com.m57.hermescontrol.R
|
||||
import com.m57.hermescontrol.data.local.AuthManager
|
||||
import com.m57.hermescontrol.data.remote.ApiClient
|
||||
import java.net.URL
|
||||
import org.json.JSONObject
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Assume.assumeTrue
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
/** Opt-in test against a fresh, isolated real cron backend, never a user server. */
|
||||
class ScheduleHttpAcceptanceTest {
|
||||
@get:Rule val compose = createComposeRule()
|
||||
|
||||
@Test
|
||||
fun editorCreatesEditsAndScheduledJobExecutesViaHttp() {
|
||||
val args = InstrumentationRegistry.getArguments()
|
||||
assumeTrue(args.getString("isolatedCronAcceptance") == "true")
|
||||
val context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
check(BuildConfig.DEBUG && context.packageName.endsWith(".dev"))
|
||||
val url = "http://127.0.0.1:18765"
|
||||
AuthManager.init(context)
|
||||
kotlinx.coroutines.runBlocking { AuthManager.awaitInitialization() }
|
||||
val oldUrl = AuthManager.getBaseUrl()
|
||||
val oldToken = AuthManager.getToken()
|
||||
val store = ViewModelStore()
|
||||
val owner = object : ViewModelStoreOwner { override val viewModelStore = store }
|
||||
try {
|
||||
AuthManager.setBaseUrl(url)
|
||||
AuthManager.setToken(null)
|
||||
ApiClient.rebuild()
|
||||
val vm = ViewModelProvider(owner)[CronJobsViewModel::class.java]
|
||||
compose.setContent {
|
||||
val state by vm.uiState.collectAsState()
|
||||
MaterialTheme {
|
||||
if (state.editorState.isOpen) {
|
||||
CronJobEditorDialog(
|
||||
state = state.editorState,
|
||||
onFieldChange = vm::updateEditorField,
|
||||
onToggleNoAgent = vm::toggleNoAgent,
|
||||
onToggleRunContinuity = vm::toggleRunContinuity,
|
||||
onSetMonitorMode = vm::setMonitorMode,
|
||||
onSelectBlueprint = vm::selectBlueprint,
|
||||
onBlueprintFieldChange = vm::updateBlueprintValue,
|
||||
onSave = vm::saveEditor,
|
||||
onDismiss = vm::closeEditor,
|
||||
onClearToast = vm::clearEditorToast,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Harmless execution fixture. Schedule is changed using the real clock below.
|
||||
compose.runOnIdle {
|
||||
vm.openNewJobDialog()
|
||||
vm.updateEditorField("name", "Android HTTP clock acceptance")
|
||||
vm.updateEditorField("script", "probe.py")
|
||||
vm.updateEditorField("schedule", "59 23 * * *")
|
||||
vm.toggleNoAgent()
|
||||
}
|
||||
compose.onNodeWithText("23:59").performScrollTo().performClick()
|
||||
compose.onNodeWithText("59").performClick()
|
||||
compose.onNode(hasContentDescription("55", substring = true)).performClick()
|
||||
compose.onNodeWithText(context.getString(R.string.action_ok)).performClick()
|
||||
compose.onNodeWithText(context.getString(R.string.cron_edit_action_save)).performClick()
|
||||
compose.waitUntil(30_000) { !vm.uiState.value.editorState.isOpen }
|
||||
compose.waitUntil(30_000) { vm.uiState.value.jobs.size == 1 }
|
||||
val id = vm.uiState.value.jobs.single().id
|
||||
compose.runOnIdle { vm.openEditJobDialog(id) }
|
||||
compose.waitUntil(30_000) { !vm.uiState.value.editorState.isLoading }
|
||||
compose.runOnIdle { assertEquals("55 23 * * *", vm.uiState.value.editorState.schedule) }
|
||||
compose.onNodeWithText("23:55").performScrollTo()
|
||||
compose.onNodeWithText(context.getString(R.string.cron_repeat_label)).performClick()
|
||||
compose.onAllNodesWithText(context.getString(R.string.cron_repeat_minutely))[0].performClick()
|
||||
compose.onNodeWithText(context.getString(R.string.cron_edit_action_save)).performClick()
|
||||
compose.waitUntil(30_000) { !vm.uiState.value.editorState.isOpen }
|
||||
val deadline = android.os.SystemClock.elapsedRealtime() + 90_000
|
||||
var evidence = JSONObject()
|
||||
while (android.os.SystemClock.elapsedRealtime() < deadline) {
|
||||
evidence = JSONObject(URL("$url/acceptance/evidence").readText())
|
||||
if (evidence.optString("result") == "PASS") break
|
||||
android.os.SystemClock.sleep(500)
|
||||
}
|
||||
assertEquals(evidence.toString(), "PASS", evidence.optString("result"))
|
||||
assertEquals(id, evidence.getString("job_id"))
|
||||
assertEquals(1, evidence.getInt("runs"))
|
||||
val requests = evidence.getJSONArray("requests")
|
||||
assertEquals(2, requests.length())
|
||||
assertEquals("55 23 * * *", requests.getJSONObject(0).getJSONObject("body").getString("schedule"))
|
||||
assertEquals("* * * * *", requests.getJSONObject(1).getJSONObject("body").getJSONObject("updates").getString("schedule"))
|
||||
assertTrue(requests.getJSONObject(1).getString("path").endsWith(id))
|
||||
} finally {
|
||||
compose.runOnIdle { store.clear() }
|
||||
AuthManager.setBaseUrl(oldUrl)
|
||||
AuthManager.setToken(oldToken)
|
||||
ApiClient.rebuild()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1950,5 +1950,5 @@
|
||||
<string name="cron_repeat_calendar_note">Seçilen günün olmadığı ay veya yıllarda çalışmaz.</string>
|
||||
<string name="cron_repeat_count">Aralık</string>
|
||||
<string name="cron_repeat_unit">Birim</string>
|
||||
<string name="cron_schedule_timezone_note">Saatler bağlı AITURK sunucusunda ayarlı saat dilimine göredir.</string>
|
||||
<string name="cron_schedule_timezone_note">Saatler bağlı AITURK sunucusunun saat dilimine göredir. Sunucunun dilimi burada okunamıyor; telefonunuzdan farklı olabilir.</string>
|
||||
</resources>
|
||||
@@ -1950,5 +1950,5 @@
|
||||
<string name="cron_repeat_calendar_note">Skips months or years without the selected date.</string>
|
||||
<string name="cron_repeat_count">Interval</string>
|
||||
<string name="cron_repeat_unit">Unit</string>
|
||||
<string name="cron_schedule_timezone_note">Times use the time zone configured on the connected AITURK server.</string>
|
||||
<string name="cron_schedule_timezone_note">Times use the connected AITURK server’s time zone. Its zone cannot be read here and may differ from your phone’s.</string>
|
||||
</resources>
|
||||
@@ -15,6 +15,8 @@ class ScheduleSelectionTest {
|
||||
"59 23 29 2 *" to ScheduleMode.YEARLY,
|
||||
"0 0 1 1 *" to ScheduleMode.YEARLY,
|
||||
"every 120m" to ScheduleMode.INTERVAL,
|
||||
"every 2h" to ScheduleMode.INTERVAL,
|
||||
"every 3d" to ScheduleMode.INTERVAL,
|
||||
)
|
||||
cases.forEach { (expression, mode) ->
|
||||
val parsed = parseSchedule(expression)
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env bash
|
||||
# Linux CI on the TurkServis server; no workstation Android installation required.
|
||||
set -euo pipefail
|
||||
sdk_root="${RUNNER_TEMP:?}/android-sdk"
|
||||
archive="${RUNNER_TEMP}/commandlinetools-linux-16111833.zip"
|
||||
mkdir -p "$sdk_root/cmdline-tools" "$sdk_root/licenses"
|
||||
curl --fail --location --retry 3 --max-time 300 \
|
||||
https://dl.google.com/android/repository/commandlinetools-linux-16111833_latest.zip -o "$archive"
|
||||
# Official checksum from Google's repository2-3.xml, cmdline-tools;latest, linux.
|
||||
printf '%s %s\n' e025545c62a8e64c7559119566a569fb1dec5f60 "$archive" | sha1sum -c -
|
||||
unzip -q "$archive" -d "$sdk_root/cmdline-tools"
|
||||
mv "$sdk_root/cmdline-tools/cmdline-tools" "$sdk_root/cmdline-tools/latest"
|
||||
cp scripts/android-licenses/android-sdk-license "$sdk_root/licenses/"
|
||||
"$sdk_root/cmdline-tools/latest/bin/sdkmanager" --sdk_root="$sdk_root" \
|
||||
'platform-tools' 'platforms;android-37.0' 'build-tools;37.0.0'
|
||||
test -f "$sdk_root/platforms/android-37.0/android.jar"
|
||||
test -x "$sdk_root/build-tools/37.0.0/aapt2"
|
||||
printf 'ANDROID_HOME=%s\nANDROID_SDK_ROOT=%s\n' "$sdk_root" "$sdk_root" >> "$GITHUB_ENV"
|
||||
Reference in new issue
Block a user