Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RunAllTests] Test coverage workflow run all tests on Origin #5519

Closed
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 .github/workflows/build_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ on:
- develop

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true

jobs:
Expand Down
33 changes: 7 additions & 26 deletions .github/workflows/code_coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ on:
- develop

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true

jobs:
Expand Down Expand Up @@ -255,10 +255,13 @@ jobs:
name: coverage-report-${{ env.SHARD_NAME }} # Saving with unique names to avoid conflict
path: coverage_reports

evaluate-code-coverage-reports:
evaluate_code_coverage_reports:
name: Evaluate Code Coverage Reports
runs-on: ubuntu-20.04
needs: code_coverage_run
# The expression if: ${{ !cancelled() }} runs a job or step regardless of its success or failure while responding to cancellations,
# serving as a cancellation-compliant alternative to if: ${{ always() }} in concurrent workflows.
if: ${{ !cancelled() }}
env:
CACHE_DIRECTORY: ~/.bazel_cache
steps:
Expand Down Expand Up @@ -305,32 +308,10 @@ jobs:
name: final-coverage-report
path: coverage_reports/CoverageReport.md

publish_coverage_report:
name: Publish Code Coverage Report
needs: evaluate-code-coverage-reports
permissions:
pull-requests: write

# The expression if: ${{ !cancelled() }} runs a job or step regardless of its success or failure while responding to cancellations,
# serving as a cancellation-compliant alternative to if: ${{ always() }} in concurrent workflows.
if: ${{ !cancelled() }}
runs-on: ubuntu-latest
steps:
- name: Download Generated Markdown Report
uses: actions/download-artifact@v4
with:
name: final-coverage-report

- name: Upload Coverage Report as PR Comment
uses: peter-evans/create-or-update-comment@v4
with:
issue-number: ${{ github.event.pull_request.number }}
body-path: 'CoverageReport.md'

# Reference: https://github.community/t/127354/7.
check_coverage_results:
name: Check Code Coverage Results
needs: [ compute_changed_files, code_coverage_run, evaluate-code-coverage-reports ]
needs: [ compute_changed_files, code_coverage_run, evaluate_code_coverage_reports ]
# The expression if: ${{ !cancelled() }} runs a job or step regardless of its success or failure while responding to cancellations,
# serving as a cancellation-compliant alternative to if: ${{ always() }} in concurrent workflows.
if: ${{ !cancelled() }}
Expand All @@ -341,5 +322,5 @@ jobs:
run: exit 1

- name: Check that coverage status is passed
if: ${{ needs.evaluate-code-coverage-reports.result != 'success' }}
if: ${{ needs.compute_changed_files.outputs.can_skip_files != 'true' && needs.evaluate_code_coverage_reports.result != 'success' }}
run: exit 1
81 changes: 81 additions & 0 deletions .github/workflows/comment_coverage_report.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Contains jobs corresponding to publishing coverage reports generated by code_coverage.yml.

name: Comment Coverage Report

# Controls when the action will run. Triggers the workflow on pull request events
# (assigned, opened, synchronize, reopened)

on:
pull_request_target:
types: [assigned, opened, synchronize, reopened]

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true

jobs:
check_code_coverage_completed:
name: Check code coverage completed
runs-on: ubuntu-latest
steps:
- name: Wait for code coverage to complete
id: wait-for-coverage
uses: ArcticLampyrid/action-wait-for-workflow@v1.2.0
with:
workflow: code_coverage.yml
sha: auto
allowed-conclusions: |
success
failure

comment_coverage_report:
name: Comment Code Coverage Report
needs: check_code_coverage_completed
permissions:
pull-requests: write

# The expression if: ${{ !cancelled() }} runs a job or step regardless of its success or failure while responding to cancellations,
# serving as a cancellation-compliant alternative to if: ${{ always() }} in concurrent workflows.
if: ${{ !cancelled() }}
runs-on: ubuntu-latest
steps:
- name: Find CI workflow run for PR
id: find-workflow-run
uses: actions/github-script@v7
continue-on-error: true
with:
script: |
// Find the last successful workflow run for the current PR's head
const { owner, repo } = context.repo;
const runsResponse = await github.rest.actions.listWorkflowRuns({
owner,
repo,
workflow_id: 'code_coverage.yml',
event: 'pull_request',
head_sha: '${{ github.event.pull_request.head.sha }}',
});

const runs = runsResponse.data.workflow_runs;
runs.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());

const run = runs[0];
if(!run) {
core.setFailed('Could not find a succesful workflow run for the PR');
return;
}

core.setOutput('run-id', run.id);

- name: Download Generated Markdown Report
uses: actions/download-artifact@v4
if: ${{ !cancelled() }} # IMPORTANT: Upload reports regardless of success or failure status
with:
name: final-coverage-report
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ steps.find-workflow-run.outputs.run-id }}

- name: Upload Coverage Report as PR Comment
uses: peter-evans/create-or-update-comment@v4
with:
issue-number: ${{ github.event.pull_request.number }}
body-path: 'CoverageReport.md'
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ on:
- develop

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true

# This workflow has the following jobs:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/static_checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ on:
- develop

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true

jobs:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/unit_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ on:
- develop

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true

jobs:
Expand Down
3 changes: 2 additions & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,8 @@ def filesToExclude = [
'**/*AppLanguageLocaleHandlerTest*.kt',
'**/*AppLanguageResourceHandlerTest*.kt',
'**/*AppLanguageWatcherMixinTest*.kt',
'**/*ActivityLanguageLocaleHandlerTest*.kt'
'**/*ActivityLanguageLocaleHandlerTest*.kt',
'**/*OptionsFragmentTest*.kt', // Excludes 2 tests.
]
_excludeSourceFiles(filesToExclude)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class PromotedStoryViewModel(
private val promotedStoryClickListener: PromotedStoryClickListener,
private val position: Int,
private val resourceHandler: AppLanguageResourceHandler,
val showClassroomLabel: Boolean,
translationController: TranslationController
) : RecentlyPlayedItemViewModel() {
/** Sets the story title of the recently played story. */
Expand All @@ -38,6 +39,12 @@ class PromotedStoryViewModel(
promotedStory.nextChapterTitle, promotedStory.nextChapterWrittenTranslationContext
)
}
/** Sets the classroom of the recently played story. */
val classroomTitle by lazy {
translationController.extractString(
promotedStory.classroomTitle, promotedStory.classroomWrittenTranslationContext
)
}

/**
* Starts [ResumeLessonActivity] if a saved exploration is selected or [ExplorationActivity] if an
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import org.oppia.android.domain.translation.TranslationController
import org.oppia.android.util.data.AsyncResult
import org.oppia.android.util.data.DataProviders.Companion.toLiveData
import org.oppia.android.util.parser.html.StoryHtmlParserEntityType
import org.oppia.android.util.platformparameter.EnableMultipleClassrooms
import org.oppia.android.util.platformparameter.PlatformParameterValue
import javax.inject.Inject

/** View model for [RecentlyPlayedFragment]. */
Expand All @@ -22,6 +24,7 @@ class RecentlyPlayedViewModel private constructor(
@StoryHtmlParserEntityType private val entityType: String,
private val resourceHandler: AppLanguageResourceHandler,
private val translationController: TranslationController,
private val enableMultipleClassrooms: PlatformParameterValue<Boolean>,
private val promotedStoryClickListener: PromotedStoryClickListener,
private val profileId: ProfileId,
) {
Expand All @@ -33,6 +36,8 @@ class RecentlyPlayedViewModel private constructor(
@StoryHtmlParserEntityType private val entityType: String,
private val resourceHandler: AppLanguageResourceHandler,
private val translationController: TranslationController,
@EnableMultipleClassrooms
private val enableMultipleClassrooms: PlatformParameterValue<Boolean>,
) {

/** Creates an instance of [RecentlyPlayedViewModel]. */
Expand All @@ -46,6 +51,7 @@ class RecentlyPlayedViewModel private constructor(
entityType,
resourceHandler,
translationController,
enableMultipleClassrooms,
promotedStoryClickListener,
profileId,
)
Expand Down Expand Up @@ -166,6 +172,7 @@ class RecentlyPlayedViewModel private constructor(
promotedStoryClickListener,
index,
resourceHandler,
enableMultipleClassrooms.value,
translationController
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import androidx.databinding.ObservableField
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations
import androidx.lifecycle.ViewModel
import org.oppia.android.app.fragment.FragmentScope
import org.oppia.android.app.model.AudioLanguage
import org.oppia.android.app.model.OppiaLanguage
import org.oppia.android.app.model.Profile
import org.oppia.android.app.model.ProfileId
Expand All @@ -20,7 +20,8 @@ import org.oppia.android.util.data.DataProviders.Companion.combineWith
import org.oppia.android.util.data.DataProviders.Companion.toLiveData
import javax.inject.Inject

/** [ViewModel] for [OptionsFragment]. */
private const val OPTIONS_ITEM_VIEW_MODEL_APP_AUDIO_LANGUAGE_PROVIDER_ID =
"OPTIONS_ITEM_VIEW_MODEL_APP_AUDIO_LANGUAGE_PROVIDER_ID"
private const val OPTIONS_ITEM_VIEW_MODEL_LIST_PROVIDER_ID =
"OPTIONS_ITEM_VIEW_MODEL_LIST_PROVIDER_ID"

Expand Down Expand Up @@ -67,11 +68,14 @@ class OptionControlsViewModel @Inject constructor(
}

private fun createOptionsItemViewModelProvider(): DataProvider<List<OptionsItemViewModel>> {
val appAudioLangProvider =
translationController.getAppLanguage(profileId).combineWith(
profileManagementController.getAudioLanguage(profileId),
OPTIONS_ITEM_VIEW_MODEL_APP_AUDIO_LANGUAGE_PROVIDER_ID
) { appLanguage, audioLanguage -> appLanguage to audioLanguage }
return profileManagementController.getProfile(profileId).combineWith(
translationController.getAppLanguage(profileId),
OPTIONS_ITEM_VIEW_MODEL_LIST_PROVIDER_ID,
::processViewModelList
)
appAudioLangProvider, OPTIONS_ITEM_VIEW_MODEL_LIST_PROVIDER_ID
) { profile, (appLang, audioLang) -> processViewModelList(profile, appLang, audioLang) }
}

private fun processViewModelListsResult(
Expand All @@ -93,12 +97,13 @@ class OptionControlsViewModel @Inject constructor(

private fun processViewModelList(
profile: Profile,
oppiaLanguage: OppiaLanguage
appLanguage: OppiaLanguage,
audioLanguage: AudioLanguage
): List<OptionsItemViewModel> {
return listOfNotNull(
createReadingTextSizeViewModel(profile),
createAppLanguageViewModel(oppiaLanguage),
createAudioLanguageViewModel(profile)
createAppLanguageViewModel(appLanguage),
createAudioLanguageViewModel(audioLanguage)
)
}

Expand All @@ -117,12 +122,14 @@ class OptionControlsViewModel @Inject constructor(
)
}

private fun createAudioLanguageViewModel(profile: Profile): OptionsAudioLanguageViewModel {
private fun createAudioLanguageViewModel(
audioLanguage: AudioLanguage
): OptionsAudioLanguageViewModel {
return OptionsAudioLanguageViewModel(
routeToAudioLanguageListListener,
loadAudioLanguageListListener,
profile.audioLanguage,
resourceHandler.computeLocalizedDisplayName(profile.audioLanguage)
audioLanguage,
resourceHandler.computeLocalizedDisplayName(audioLanguage)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import org.oppia.android.R
import org.oppia.android.app.fragment.FragmentScope
import org.oppia.android.app.model.AudioLanguage
import org.oppia.android.app.model.CellularDataPreference
import org.oppia.android.app.model.Profile
import org.oppia.android.app.model.ProfileId
import org.oppia.android.app.model.Spotlight
import org.oppia.android.app.model.State
Expand Down Expand Up @@ -147,15 +146,15 @@ class AudioFragmentPresenter @Inject constructor(
) as? SpotlightManager
}

private fun getProfileData(): LiveData<String> {
private fun retrieveAudioLanguageCode(): LiveData<String> {
return Transformations.map(
profileManagementController.getProfile(profileId).toLiveData(),
::processGetProfileResult
profileManagementController.getAudioLanguage(profileId).toLiveData(),
::processAudioLanguageResult
)
}

private fun subscribeToAudioLanguageLiveData() {
getProfileData().observe(
retrieveAudioLanguageCode().observe(
activity,
Observer<String> { result ->
audioViewModel.selectedLanguageCode = result
Expand All @@ -165,11 +164,9 @@ class AudioFragmentPresenter @Inject constructor(
}

/** Gets language code by [AudioLanguage]. */
private fun getAudioLanguage(audioLanguage: AudioLanguage): String {
private fun computeLanguageCode(audioLanguage: AudioLanguage): String {
return when (audioLanguage) {
AudioLanguage.HINDI_AUDIO_LANGUAGE -> "hi"
AudioLanguage.FRENCH_AUDIO_LANGUAGE -> "fr"
AudioLanguage.CHINESE_AUDIO_LANGUAGE -> "zh"
AudioLanguage.BRAZILIAN_PORTUGUESE_LANGUAGE -> "pt"
AudioLanguage.ARABIC_LANGUAGE -> "ar"
AudioLanguage.NIGERIAN_PIDGIN_LANGUAGE -> "pcm"
Expand All @@ -178,16 +175,16 @@ class AudioFragmentPresenter @Inject constructor(
}
}

private fun processGetProfileResult(profileResult: AsyncResult<Profile>): String {
val profile = when (profileResult) {
private fun processAudioLanguageResult(languageResult: AsyncResult<AudioLanguage>): String {
val audioLanguage = when (languageResult) {
is AsyncResult.Failure -> {
oppiaLogger.e("AudioFragment", "Failed to retrieve profile", profileResult.error)
Profile.getDefaultInstance()
oppiaLogger.e("AudioFragment", "Failed to retrieve audio language", languageResult.error)
AudioLanguage.AUDIO_LANGUAGE_UNSPECIFIED
}
is AsyncResult.Pending -> Profile.getDefaultInstance()
is AsyncResult.Success -> profileResult.value
is AsyncResult.Pending -> AudioLanguage.AUDIO_LANGUAGE_UNSPECIFIED
is AsyncResult.Success -> languageResult.value
}
return getAudioLanguage(profile.audioLanguage)
return computeLanguageCode(audioLanguage)
}

/** Sets selected language code in presenter and ViewModel. */
Expand Down
Loading
Loading