Skip to content
Draft
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
30 changes: 30 additions & 0 deletions .github/workflows/root-locale.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Root locale

on:
pull_request:
push:
branches:
- main
- rc
workflow_dispatch:

permissions:
contents: read

jobs:
root-locale:
name: Every case change in the main code names its locale
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
persist-credentials: false

- name: Test the check
shell: pwsh
run: Invoke-Pester -Path ./ci/check-root-locale.Tests.ps1 -CI

- name: Check the case changes
shell: pwsh
run: ./ci/check-root-locale.ps1
98 changes: 98 additions & 0 deletions ci/check-root-locale.Tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# Tests for ci/check-root-locale.ps1.

BeforeAll {
$script:Check = Join-Path $PSScriptRoot 'check-root-locale.ps1'
. $script:Check
}

Describe 'Find-DefaultLocaleCaseChange' {

It 'finds <Line>' -ForEach @(
@{ Line = 'String key = property.getName().toLowerCase();' }
@{ Line = 'if (name.toUpperCase().equals(other)) {' }
@{ Line = 'key.toLowerCase( ).contains("x")' }
) {
Find-DefaultLocaleCaseChange -Path 'A.java' -Text $Line |
Should -Be "A.java:1: $Line"
}

It 'leaves <Line> alone' -ForEach @(
@{ Line = 'String key = property.getName().toLowerCase(Locale.ROOT);' }
@{ Line = 'name.toUpperCase(Locale.ENGLISH)' }
@{ Line = 'a.equalsIgnoreCase(b)' }
@{ Line = 'Character.toLowerCase(c)' }
) {
Find-DefaultLocaleCaseChange -Path 'A.java' -Text $Line |
Should -BeNullOrEmpty
}

It 'gives each line number' {
$text = "class A {`n String a = b.toLowerCase();`r`n String c = d;`n" +
" String e = f.toUpperCase();`n}"
Find-DefaultLocaleCaseChange -Path 'p/A.java' -Text $text |
Should -Be @(
'p/A.java:2: String a = b.toLowerCase();'
'p/A.java:4: String e = f.toUpperCase();')
}

It 'finds nothing in an empty file' {
Find-DefaultLocaleCaseChange -Path 'A.java' -Text '' |
Should -BeNullOrEmpty
}
}

Describe 'Test-MainSource' {

It 'reads <Path>' -ForEach @(
@{ Path = 'pipeline/pipeline.core/src/main/java/a/B.java' }
@{ Path = 'src/main/java/B.java' }
@{ Path = 'examples/device-detection/shared/src/main/java/a/B.java' }
) {
Test-MainSource $Path | Should -BeTrue
}

It 'leaves out <Path>' -ForEach @(
@{ Path = 'pipeline/pipeline.core/src/test/java/a/B.java' }
@{ Path = 'pipeline/pipeline.core/src/main/resources/a.json' }
@{ Path = 'ci/check-root-locale.ps1' }
@{ Path = 'docs/src/main.java.md' }
) {
Test-MainSource $Path | Should -BeFalse
}
}

Describe 'The check' {

BeforeAll {
$script:Repo = Join-Path ([IO.Path]::GetTempPath()) ([guid]::NewGuid())
New-Item -ItemType Directory -Path $script:Repo | Out-Null
& git -C $script:Repo init -q
$main = Join-Path $script:Repo 'm/src/main/java'
$test = Join-Path $script:Repo 'm/src/test/java'
New-Item -ItemType Directory -Path $main, $test | Out-Null
Set-Content -LiteralPath (Join-Path $test 'T.java') `
-Value 'class T { String a = b.toLowerCase(); }'
Set-Content -LiteralPath (Join-Path $main 'M.java') `
-Value 'class M { String a = b.toLowerCase(Locale.ROOT); }'
& git -C $script:Repo add -A
}

AfterAll {
Remove-Item -LiteralPath $script:Repo -Recurse -Force
}

It 'passes when every main case change names its locale' {
$output = & pwsh -NoProfile -File $script:Check -RepoRoot $script:Repo
$LASTEXITCODE | Should -Be 0
($output -join "`n") | Should -Match 'Every case change in 1 main'
}

It 'fails on a main case change with no locale, naming the line' {
Set-Content -LiteralPath (Join-Path $script:Repo 'm/src/main/java/N.java') `
-Value "class N {`n String a = b.toUpperCase();`n}"
& git -C $script:Repo add -A
$output = & pwsh -NoProfile -File $script:Check -RepoRoot $script:Repo
$LASTEXITCODE | Should -Be 1
$output | Should -Contain 'm/src/main/java/N.java:2: String a = b.toUpperCase();'
}
}
67 changes: 67 additions & 0 deletions ci/check-root-locale.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Every case change in the main code names its locale. Java's toLowerCase()
# and toUpperCase() with no argument use the default locale, and under a
# Turkish or Azeri default a capital I lowers to a dotless i, so a name that
# holds one no longer matches its lower case form. This reads every Java file
# git tracks under a src/main folder, which leaves out the tests, build output
# and the submodules, and fails on any call that names no locale. Pass
# Locale.ROOT.
#
# Dot-sourcing the script defines Find-DefaultLocaleCaseChange and
# Test-MainSource without reading the repository, which is how its tests use
# them.

[CmdletBinding()]
param(
# The root of the checkout to read. The default is the repository this
# script lives in.
[string]$RepoRoot = (Join-Path $PSScriptRoot '..')
)

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

$script:DefaultLocalePattern = [regex]::new('\.to(?:Lower|Upper)Case\(\s*\)')

function Find-DefaultLocaleCaseChange {
# One "<path>:<line>: <text>" for each line of the text with a case
# change that names no locale.
param(
[Parameter(Mandatory)][string]$Path,
[Parameter(Mandatory)][AllowEmptyString()][string]$Text
)
$lines = $Text -split "`r?`n"
for ($i = 0; $i -lt $lines.Count; $i++) {
if ($script:DefaultLocalePattern.IsMatch($lines[$i])) {
"${Path}:$($i + 1): $($lines[$i].Trim())"
}
}
}

function Test-MainSource {
# Whether a path as git gives it is a Java file under a src/main folder.
param([Parameter(Mandatory)][string]$Path)
$Path.EndsWith('.java') -and "/$Path".Contains('/src/main/')
}

if ($MyInvocation.InvocationName -eq '.') {
return
}

$root = (Resolve-Path $RepoRoot).Path
$tracked = & git -C $root -c core.quotePath=false ls-files
if ($LASTEXITCODE -ne 0) {
throw "git ls-files failed in $root"
}
$files = @($tracked | Where-Object { Test-MainSource $_ })
$found = @(foreach ($file in $files) {
$text = [string](Get-Content -Raw -LiteralPath (Join-Path $root $file))
Find-DefaultLocaleCaseChange -Path $file -Text $text
})
if ($found.Count -gt 0) {
$found | ForEach-Object { Write-Output $_ }
Write-Output ("$($found.Count) case changes name no locale. Pass " +
'Locale.ROOT, because the default locale can lower a capital I ' +
'to a dotless i.')
exit 1
}
Write-Output "Every case change in $($files.Count) main source files names its locale."
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;
Expand Down Expand Up @@ -272,7 +273,7 @@ private boolean loadAspectProperties(CloudRequestEngine engine) {
private AspectPropertyValue<Integer> getIntegerAspectPropertyValue(
JSONObject deviceObj,
AspectPropertyMetaData property) {
String key = property.getName().toLowerCase();
String key = property.getName().toLowerCase(Locale.ROOT);
AspectPropertyValue<Integer> intValue = new AspectPropertyValueDefault<>();
if(deviceObj.isNull(key)){
intValue.setNoValueMessage(getNoValueReason(deviceObj, key));
Expand All @@ -284,7 +285,7 @@ private AspectPropertyValue<Integer> getIntegerAspectPropertyValue(
}

private AspectPropertyValue<Double> getDoubleAspectPropertyValue(JSONObject deviceObj, AspectPropertyMetaData property) {
String key = property.getName().toLowerCase();
String key = property.getName().toLowerCase(Locale.ROOT);
AspectPropertyValue<Double> doubleValue = new AspectPropertyValueDefault<>();
if(deviceObj.isNull(key)){
doubleValue.setNoValueMessage(getNoValueReason(deviceObj, key));
Expand All @@ -306,7 +307,7 @@ private AspectPropertyValue<Double> getDoubleAspectPropertyValue(JSONObject devi
private AspectPropertyValue<Boolean> getBooleanAspectPropertyValue(
JSONObject deviceObj,
AspectPropertyMetaData property) {
String key = property.getName().toLowerCase();
String key = property.getName().toLowerCase(Locale.ROOT);
AspectPropertyValue<Boolean> booleanValue = new AspectPropertyValueDefault<>();
if(deviceObj.isNull(key)){
booleanValue.setNoValueMessage(getNoValueReason(deviceObj, key));
Expand All @@ -328,7 +329,7 @@ private AspectPropertyValue<Boolean> getBooleanAspectPropertyValue(
private AspectPropertyValue<List<String>> getListAspectPropertyValue(
JSONObject deviceObj,
AspectPropertyMetaData property){
String key = property.getName().toLowerCase();
String key = property.getName().toLowerCase(Locale.ROOT);
AspectPropertyValue<List<String>> listValue = new AspectPropertyValueDefault<>();
if(deviceObj.isNull(key))
{
Expand Down Expand Up @@ -357,7 +358,7 @@ private AspectPropertyValue<List<String>> getListAspectPropertyValue(
private AspectPropertyValue<JavaScript> getJavaScriptAspectPropertyValue(
JSONObject deviceObj,
AspectPropertyMetaData property){
String key = property.getName().toLowerCase();
String key = property.getName().toLowerCase(Locale.ROOT);
AspectPropertyValue<JavaScript> jsValue = new AspectPropertyValueDefault<>();
if(deviceObj.isNull(key)){
jsValue.setNoValueMessage(getNoValueReason(deviceObj, key));
Expand All @@ -378,7 +379,7 @@ private AspectPropertyValue<JavaScript> getJavaScriptAspectPropertyValue(
private AspectPropertyValue<String> getStringAspectPropertyValue(
JSONObject deviceObj,
AspectPropertyMetaData property){
String key = property.getName().toLowerCase();
String key = property.getName().toLowerCase(Locale.ROOT);
AspectPropertyValue<String> stringValue = new AspectPropertyValueDefault<>();
if(deviceObj.isNull(key)){
stringValue.setNoValueMessage(getNoValueReason(deviceObj, key));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

import static org.junit.Assert.*;
import static org.mockito.ArgumentMatchers.*;
Expand Down Expand Up @@ -129,6 +130,59 @@ public void PropertyNotInResource() throws Exception {
}
}

/**
* A property whose name holds a capital I is read from the cloud's lower
* case key under a Turkish default locale, which would otherwise lower
* the I to a dotless i and look for a key the response does not have.
*
* @throws Exception
*/
@Test
public void PropertyWithCapitalI_TurkishLocale_HasValue() throws Exception {
Locale original = Locale.getDefault();
Locale.setDefault(Locale.forLanguageTag("tr-TR"));
try (DeviceDetectionCloudEngine engine =
new DeviceDetectionCloudEngineBuilder(loggerFactory)
.build()) {

FlowData flowData = mock(FlowData.class);
addResponse(flowData, deviceIdJson);
DeviceDataCloud device = new DeviceDataCloudInternal(
loggerFactory.getLogger(DeviceDataCloud.class.getSimpleName()),
flowData,
engine,
mock(MissingPropertyService.class));
engine.cloudRequestEngine = mock(CloudRequestEngine.class);
engine.aspectProperties = deviceIdProperties;

engine.processEngine(flowData, device);

AspectPropertyValue<String> deviceId = device.getDeviceId();
assertTrue(deviceId.hasValue());
assertEquals(expectedDeviceId, deviceId.getValue());
} finally {
Locale.setDefault(original);
}
}

private static String expectedDeviceId = "12280-48866-24305-18092";

private static String deviceIdJson =
"{\n" +
" 'device': {\n" +
" 'deviceid': '" + expectedDeviceId + "'\n" +
" },\n" +
" 'javascriptProperties': []\n" +
"}\n";

private static List<AspectPropertyMetaData> deviceIdProperties =
new ArrayList<AspectPropertyMetaData>() {
private static final long serialVersionUID = 1L;
{
add(new AspectPropertyMetaDataDefault("DeviceId", null, null, String.class, null, true));
}
};

private static String expectedNullReason = "this is the null reason";
private static String expectedMissingPropertyReason = "this is the missing reason";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import org.slf4j.Logger;

import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;

Expand Down Expand Up @@ -147,7 +148,7 @@ public Map<String, Object> asKeyMap() {
for (ElementPropertyMetaData property : getPipeline()
.getElementAvailableProperties()
.get(getEngines().get(0).getElementDataKey()).values()) {
map.put(property.getName().toLowerCase(),
map.put(property.getName().toLowerCase(Locale.ROOT),
getAs(property.getName(),
AspectPropertyValue.class,
property.getType()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
package fiftyone.devicedetection.shared.testhelpers;

import java.util.Base64;
import java.util.Locale;
import java.util.Objects;

/**
Expand All @@ -37,7 +38,7 @@ public class KeyUtils {
public static String getNamedKey(String keyName) {
String resourceKey = System.getenv(keyName);
if (Objects.isNull(resourceKey)) {
resourceKey = System.getenv(keyName.toUpperCase());
resourceKey = System.getenv(keyName.toUpperCase(Locale.ROOT));
if (Objects.isNull(resourceKey)) {
resourceKey = System.getProperty(keyName);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import java.nio.file.Files;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.Random;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -215,8 +216,8 @@ private static Map<String, String> filterEvidence(Map<String, String> evidence,
*
* @return a random IPv4 address string
*/
private static String generateRandomIp() {
return String.format("%d.%d.%d.%d",
static String generateRandomIp() {
return String.format(Locale.ROOT, "%d.%d.%d.%d",
random.nextInt(256),
random.nextInt(256),
random.nextInt(256),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import java.io.PrintWriter;
import java.nio.file.*;
import java.text.SimpleDateFormat;
import java.util.Locale;
import java.util.Objects;
import java.util.Scanner;

Expand Down Expand Up @@ -277,7 +278,7 @@ static void run(String dataFilename, String licenseKey, boolean interactive) thr
"in your licensed allocation.");
System.out.print("Do you wish to continue with this example (y)? ");
String input = scanner.nextLine();
if ((input.isEmpty() || input.toLowerCase().startsWith("y")) == false) {
if ((input.isEmpty() || input.toLowerCase(Locale.ROOT).startsWith("y")) == false) {
logger.info("Stopping example without download");
return;
}
Expand Down
Loading
Loading