Skip to content

Commit 18233c6

Browse files
Add comprehensive line break quality tests for SciMLStyle
- Add helper function analyze_line_quality to measure line utilization efficiency - Test single line preservation for simple expressions - Test efficient breaking of long lines with good space utilization - Test type parameter wrapping to avoid excessive breaks - Test array operation formatting preserves structure - Test function definitions break intelligently based on length - Test nested expressions maintain logical hierarchy - Test mathematical expressions break at natural operator boundaries - Test to avoid orphan parameters on their own lines - Test keyword argument grouping for readability - Test preservation of logical grouping (coordinates, pairs) - Test real-world SciML examples for practical formatting quality These tests ensure the formatter produces aesthetically pleasing output that makes efficient use of the 92-character line limit while maintaining code readability and structure.
1 parent 8fff0da commit 18233c6

File tree

1 file changed

+292
-0
lines changed

1 file changed

+292
-0
lines changed

test/sciml_style.jl

Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1142,4 +1142,296 @@
11421142
formatted = format_text(str, SciMLStyle())
11431143
@test contains(formatted, "quote")
11441144
end
1145+
1146+
@testset "line break quality" begin
1147+
# Helper function to analyze line utilization
1148+
function analyze_line_quality(formatted_code)
1149+
lines = split(formatted_code, '\n')
1150+
non_empty_lines = filter(l -> !isempty(strip(l)), lines)
1151+
1152+
if isempty(non_empty_lines)
1153+
return (max_length=0, avg_length=0.0, num_lines=0, efficiency=0.0)
1154+
end
1155+
1156+
lengths = [length(rstrip(line)) for line in non_empty_lines]
1157+
max_length = maximum(lengths)
1158+
avg_length = sum(lengths) / length(lengths)
1159+
1160+
# Efficiency: how well lines use available space (0-1)
1161+
# Penalize both very short lines and lines over margin
1162+
efficiency_scores = map(lengths) do len
1163+
if len > 92
1164+
0.5 * (92 / len) # Penalty for exceeding margin
1165+
elseif len < 20
1166+
len / 40 # Penalty for very short lines
1167+
else
1168+
len / 92 # Normal efficiency
1169+
end
1170+
end
1171+
efficiency = sum(efficiency_scores) / length(efficiency_scores)
1172+
1173+
return (max_length=max_length, avg_length=avg_length,
1174+
num_lines=length(non_empty_lines), efficiency=efficiency)
1175+
end
1176+
1177+
# Test 1: Simple expressions should stay on one line when they fit
1178+
@testset "single line preservation" begin
1179+
# These should remain on one line
1180+
single_line_cases = [
1181+
"x = a + b * c - d",
1182+
"result = solve(prob, alg)",
1183+
"arr[i, j] = value * scale",
1184+
"y = sin(x) + cos(z) * exp(-t)",
1185+
"data = [1, 2, 3, 4, 5]",
1186+
]
1187+
1188+
for code in single_line_cases
1189+
formatted = format_text(code, SciMLStyle())
1190+
@test !contains(formatted, '\n')
1191+
@test formatted == code
1192+
end
1193+
end
1194+
1195+
# Test 2: Long lines should break efficiently
1196+
@testset "efficient line breaking" begin
1197+
# Function call with many parameters
1198+
str = """
1199+
result = solve(NonlinearProblem(f, u0, p), NewtonRaphson(; autodiff=AutoForwardDiff()), abstol=1e-10, reltol=1e-10, maxiters=1000, show_trace=true)
1200+
"""
1201+
formatted = format_text(str, SciMLStyle())
1202+
quality = analyze_line_quality(formatted)
1203+
1204+
@test quality.max_length <= 92 # Should respect margin
1205+
@test quality.avg_length > 50 # Should use space efficiently
1206+
@test quality.efficiency > 0.6 # Good space utilization
1207+
1208+
# Mathematical expression
1209+
str = """
1210+
residual = alpha * (u[i-1, j] + u[i+1, j] - 2u[i, j]) / dx^2 + beta * (u[i, j-1] + u[i, j+1] - 2u[i, j]) / dy^2 + gamma * u[i, j]
1211+
"""
1212+
formatted = format_text(str, SciMLStyle())
1213+
quality = analyze_line_quality(formatted)
1214+
1215+
# Should break but maintain good line density
1216+
@test quality.num_lines >= 2
1217+
@test quality.avg_length > 40 # Not too many short lines
1218+
end
1219+
1220+
# Test 3: Type parameters should wrap nicely
1221+
@testset "type parameter wrapping" begin
1222+
str = """
1223+
function f(x::AbstractArray{T,N}, y::AbstractMatrix{S}) where {T<:Real, S<:Number, N}
1224+
return x
1225+
end
1226+
"""
1227+
formatted = format_text(str, SciMLStyle())
1228+
quality = analyze_line_quality(formatted)
1229+
1230+
# Should fit on 3 lines max
1231+
@test quality.num_lines <= 3
1232+
@test quality.max_length <= 92
1233+
1234+
# Long type parameter list
1235+
str = """
1236+
struct MyType{T1,T2,T3,T4,T5,T6,T7,T8,T9,T10} <: AbstractType{T1,T2,T3,T4,T5,T6,T7,T8,T9,T10}
1237+
end
1238+
"""
1239+
formatted = format_text(str, SciMLStyle())
1240+
lines = split(formatted, '\n')
1241+
1242+
# Should break but not excessively
1243+
@test length(lines) <= 4
1244+
# No single parameter on a line
1245+
for line in lines
1246+
if contains(line, "T") && !contains(line, "end")
1247+
@test contains(line, ",") || contains(line, "{") || contains(line, "}")
1248+
end
1249+
end
1250+
end
1251+
1252+
# Test 4: Array operations should maintain structure
1253+
@testset "array operation formatting" begin
1254+
# Array comprehension
1255+
str = "[f(i, j) * g(k) for i in 1:n, j in 1:m, k in 1:p if condition(i, j, k)]"
1256+
formatted = format_text(str, SciMLStyle())
1257+
1258+
# Should keep comprehension readable
1259+
@test contains(formatted, "for i in 1:n, j in 1:m")
1260+
quality = analyze_line_quality(formatted)
1261+
@test quality.efficiency > 0.5
1262+
1263+
# Matrix literal
1264+
str = """
1265+
A = [
1266+
a11 a12 a13 a14
1267+
a21 a22 a23 a24
1268+
a31 a32 a33 a34
1269+
]
1270+
"""
1271+
formatted = format_text(str, SciMLStyle())
1272+
# Should preserve matrix structure
1273+
@test contains(formatted, "a11 a12 a13 a14")
1274+
@test contains(formatted, "a21 a22 a23 a24")
1275+
end
1276+
1277+
# Test 5: Function definitions with multiple arguments
1278+
@testset "function definition formatting" begin
1279+
# Moderate length - should stay on 2-3 lines
1280+
str = """
1281+
function solve(prob::ODEProblem, alg::Tsit5; abstol=1e-6, reltol=1e-3, saveat=[], callback=nothing)
1282+
# body
1283+
end
1284+
"""
1285+
formatted = format_text(str, SciMLStyle())
1286+
quality = analyze_line_quality(formatted)
1287+
1288+
@test quality.num_lines <= 4 # Reasonable number of lines
1289+
@test quality.avg_length > 30 # Reasonable line utilization
1290+
1291+
# Very long parameter list
1292+
str = """
1293+
function complex_solver(problem::NonlinearProblem{uType,tType,isinplace}, algorithm::NewtonRaphson{CS,AD,FDT,L,P,ST,CJ}, x0::AbstractVector, p::NamedTuple; abstol::Float64=1e-8, reltol::Float64=1e-8, maxiters::Int=1000, callback=nothing, show_trace::Bool=false) where {uType,tType,isinplace,CS,AD,FDT,L,P,ST,CJ}
1294+
# body
1295+
end
1296+
"""
1297+
formatted = format_text(str, SciMLStyle())
1298+
lines = split(formatted, '\n')
1299+
1300+
# Should break intelligently
1301+
@test all(length(rstrip(line)) <= 92 for line in lines)
1302+
# But not too many lines
1303+
@test length(lines) <= 12 # Complex function may need more lines
1304+
end
1305+
1306+
# Test 6: Nested expressions should maintain hierarchy
1307+
@testset "nested expression formatting" begin
1308+
str = """
1309+
result = transform(integrate(differentiate(interpolate(data, method=:cubic), order=2), bounds=(a, b)), scaling=:log)
1310+
"""
1311+
formatted = format_text(str, SciMLStyle())
1312+
1313+
# Should break at outer function calls first
1314+
@test contains(formatted, "transform(")
1315+
quality = analyze_line_quality(formatted)
1316+
@test quality.efficiency > 0.5
1317+
1318+
# Nested array access
1319+
str = "value = data[indices[i]][subindices[j]][k]"
1320+
formatted = format_text(str, SciMLStyle())
1321+
# Should keep array access together when reasonable
1322+
@test !contains(formatted, "[\n")
1323+
end
1324+
1325+
# Test 7: Mathematical expressions should break at operators
1326+
@testset "mathematical expression breaking" begin
1327+
# Long equation
1328+
str = """
1329+
energy = 0.5 * mass * velocity^2 + mass * gravity * height + 0.5 * spring_constant * displacement^2 + friction_coefficient * mass * gravity * distance
1330+
"""
1331+
formatted = format_text(str, SciMLStyle())
1332+
lines = split(formatted, '\n')
1333+
1334+
# Should break at + operators
1335+
for line in lines[2:end]
1336+
stripped = strip(line)
1337+
if !isempty(stripped)
1338+
# Continuation lines should start with operator or be aligned
1339+
@test startswith(stripped, "+") || startswith(stripped, "-") ||
1340+
startswith(stripped, "*") || startswith(stripped, "/") ||
1341+
length(lstrip(line)) < length(line) # Or be indented
1342+
end
1343+
end
1344+
1345+
quality = analyze_line_quality(formatted)
1346+
@test quality.avg_length > 40 # Efficient use of space
1347+
end
1348+
1349+
# Test 8: Avoid "orphan" parameters
1350+
@testset "avoid orphan parameters" begin
1351+
# Should not leave single parameters on lines
1352+
str = "f(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z)"
1353+
formatted = format_text(str, SciMLStyle())
1354+
lines = split(formatted, '\n')
1355+
1356+
for line in lines
1357+
stripped = strip(line)
1358+
# If it's just a single letter and comma/paren, it's an orphan
1359+
if match(r"^[a-z],?\)?$", stripped) !== nothing
1360+
@test false # Should not have orphan parameters
1361+
end
1362+
end
1363+
end
1364+
1365+
# Test 9: Keyword arguments should group nicely
1366+
@testset "keyword argument grouping" begin
1367+
str = """
1368+
solve(prob; abstol=1e-8, reltol=1e-8, dtmin=1e-10, dtmax=0.1, maxiters=10000, adaptive=true, save_everystep=false, save_start=true, save_end=true, verbose=true)
1369+
"""
1370+
formatted = format_text(str, SciMLStyle())
1371+
1372+
# Should group related kwargs when possible
1373+
lines = split(formatted, '\n')
1374+
# First line should have multiple kwargs if they fit
1375+
if length(lines) > 1
1376+
@test contains(lines[1], ",") || contains(lines[1], ";")
1377+
end
1378+
1379+
quality = analyze_line_quality(formatted)
1380+
@test quality.efficiency > 0.6
1381+
end
1382+
1383+
# Test 10: Preserve logical grouping
1384+
@testset "logical grouping preservation" begin
1385+
# Coordinates should stay together
1386+
str = "plot(x1, y1, x2, y2, x3, y3; xlabel=\"Time\", ylabel=\"Value\", title=\"Results\")"
1387+
formatted = format_text(str, SciMLStyle())
1388+
1389+
# x,y pairs should stay together when possible
1390+
@test contains(formatted, "x1, y1") || contains(formatted, "x1,y1")
1391+
1392+
# Matrix multiplication chain
1393+
str = "result = A * B * C * D * E * F"
1394+
formatted = format_text(str, SciMLStyle())
1395+
# Should keep some products together
1396+
@test length(split(formatted, '\n')) <= 3
1397+
end
1398+
1399+
# Test 11: Real-world examples from SciML
1400+
@testset "SciML real-world examples" begin
1401+
# From OrdinaryDiffEq
1402+
str = """
1403+
@muladd function perform_step!(integrator, cache::RK4Cache, repeat_step=false)
1404+
@unpack t, dt, uprev, u, f, p = integrator
1405+
@unpack k1, k2, k3, k4, tmp = cache
1406+
f(k1, uprev, p, t)
1407+
f(k2, uprev + dt/2 * k1, p, t + dt/2)
1408+
f(k3, uprev + dt/2 * k2, p, t + dt/2)
1409+
f(k4, uprev + dt * k3, p, t + dt)
1410+
@. u = uprev + dt/6 * (k1 + 2*k2 + 2*k3 + k4)
1411+
end
1412+
"""
1413+
formatted = format_text(str, SciMLStyle())
1414+
quality = analyze_line_quality(formatted)
1415+
1416+
# Should maintain readability
1417+
@test quality.max_length <= 92
1418+
@test quality.avg_length > 30
1419+
1420+
# From ModelingToolkit
1421+
str = """
1422+
@parameters t σ ρ β
1423+
@variables x(t) y(t) z(t)
1424+
D = Differential(t)
1425+
eqs = [D(x) ~ σ * (y - x), D(y) ~ x * (ρ - z) - y, D(z) ~ x * y - β * z]
1426+
"""
1427+
formatted = format_text(str, SciMLStyle())
1428+
1429+
# Should keep equations readable
1430+
@test contains(formatted, "D(x) ~ σ * (y - x)")
1431+
lines = split(formatted, '\n')
1432+
# Equations should be nicely formatted
1433+
eq_lines = filter(l -> contains(l, "D("), lines)
1434+
@test length(eq_lines) > 0
1435+
end
1436+
end
11451437
end

0 commit comments

Comments
 (0)