mirror of
https://github.com/ossrs/srs.git
synced 2026-10-24 09:59:20 +03:00
Adds `srs-integ`, a black-box integration suite for the SRS media server, and fixes the SRS bugs it found: over 120 of them, in every protocol. The suite tests SRS from outside the process, only through what a client, an operator or a callback server can see, so a rewritten server could pass the same cases. The suite now has about 1,450 cases (1,353 main and 99 timer), with no known bugs left: every bug a case found is fixed in this PR. It also removes some unused features, adds env support for a few more directives, and changes a few hook and API fields. ## srs-integ suite `cmd/integ` is a Go module of its own, `srs-integ`, separate from the root `srsx` module. It imports nothing from the repository. - **Run:** `make integ` makes the test media, then runs the cases in two `go test` processes at once: the main cases and the `TestTimer_` cases. `make integ-unit` runs the unit tests of the clients and analyzers, and `bin/srs-integ` gives `probe`, `publish` and `prepare` from the command line. - **Server adapter:** each server is `objs/srs -e`, configured only by env vars, on ports from `SRS_INTEG_PORTS` (14000-14999 in the Port Plan). Most cases share a Common server, split into 4 shards. Cases that need other settings run on variant servers, and groups share these where they can. - **Case classes:** Fast (≤100ms), Normal (≤500ms) and Timer (≤10s, only in `TestTimer_` groups). A case that runs over its limit fails. `KnownBug` marks a case that fails because of an SRS bug, and the case fails once the bug is fixed, so the mark can't go stale. - **Clients and analyzers written for the suite:** RTMP and RTMPS (both handshakes, any chunk size, enhanced RTMP, malformed input), FLV, TS, AAC/MP3, MP4, HLS (TS, fMP4, AES-128, SAMPLE-AES) and DASH. Each one fills a shared stream report: codecs, picture size, sequence headers, keyframes, timestamps and A/V sync. - **Clients built on libraries:** WebRTC WHIP/WHEP on pion (UDP and TCP, NACK/RTX/TWCC recorder, raw DTLS sessions), SRT on gosrt, RTSP on gortsplib, and a GB28181 PS-over-RTP publisher with no SIP. - **Test media:** generated from `doc/source.flv` by `make integ-prepare` and never committed. The files cover H.264, HEVC, VP9, AV1, AAC, Opus, MP3 and G.711, in FLV, MP4 and TS. A manifest keeps the media from being made again when nothing changed. - **Feature coverage:** the HTTP API, HTTPS API and exporter, reload, heartbeat, RTMP, RTMPS, HTTP-FLV, HTTP-TS, HTTP AAC and MP3, HTTPS, HLS, DASH, WebRTC, SRT, RTSP, GB28181, the MPEG-TS over UDP caster, every HTTP hook, security rules, forward, edge, the origin cluster, DVR, static files and VOD, signals, the command line and env settings. - **Conversion matrix:** each source (RTMP/RTMPS, WHIP, SRT, GB28181) is played by each player (RTMP, HTTP-FLV, HTTP-TS, MP3, AAC, HLS ts and fMP4, DASH, WHEP, SRT, RTSP) with each codec it can carry. The README has the matrix. - **Scale:** `-integ.scale=N` runs each case N times. A run of about 1,000 cases passes in about 11s. ## SRS bug fixes Every fix has a utest and, where a client can see it, an srs-integ case that failed before the fix. The main ones by area: - **HLS:** fMP4: - The default init file is now listed by its dir. - `hls_entry_prefix` now applies to fMP4 URIs. - MP3 streams now get an audio track in the init file. - Each publish now starts with a discontinuity. - `hls_keys` now encrypts the samples with cbcs. - A segment that fails to open is now dropped. Before, it crashed SRS. Other HLS fixes: key URIs, AES padding, HLS starts again after `hls_dispose`, the ratios, a TS republish, session names, and the `hls_ctx` of another stream's session is now checked as a new session. - **DASH:** the config, the MPD (window, update period, HEVC and MP3 codecs), init files of MP3 and of one-track streams, and dispose. - **WebRTC:** - Keyframes with an SEI, and Sender Reports and their mapping. - Opus timing, G.711, and WHEP SSRCs. - A refused WHIP publisher now frees its stream. - The start and hooks, and WebRTC over TCP. - **SRT:** only the configured latencies are set, as ffmpeg does. Also fixed: HEVC headers and CTS, frame counts, and the listener. - **RTSP:** a republish to a kept player, PLAY without SETUP, unknown methods, closes, DESCRIBE/TEARDOWN, request bodies and `on_stop`. - **GB28181:** - SSRC parsing. - HEVC to RTMP is no longer sent in legacy FLV tags. - A crash on a second media connection. - Latency with no audio, sliced frames, CTS, session lifetime, and the muxer and caster output. - **RTMP and HTTP:** - Timestamps that jump back without time correction no longer stop a player. - The republish param and the publish timeout after a pause. - The no-origin close and the RTMPS port. - HTTP ranges, including MP4 suffix ranges and VOD 416. - Redirects, mounts, unmounts and URL `..`. - CORS, API auth paths, JSONP 400 and 403 answers. - Static files and the request echo. - **Hooks:** - The heartbeat URL's query is now sent. - `on_play` clients and client types, and `on_dvr`. - A client that `on_connect` refused no longer gets `on_close`. - **Other:** DVR (MP4 info frame, audio rate, audio cut), listen ports above 65535, the env vhost check, the UDP caster, process stats, the tcmalloc rate, the encoder loop, the edge failover, STUN, coroutine joins, and the resource manager's fast-id cache (WebRTC and GB lookups never used it). ## Behavior changes - **Hooks:** - `on_publish`, `on_unpublish`, `on_play` and `on_stop` have a new `type` field after `client_id`, such as `rtmp-play` or `hls-play`, the same value as `/api/v1/clients`. - Every protocol now sends `param` with its leading `?`. - HTTP players send their own `http(s)://` tcUrl, and WHIP/WHEP clients send `webrtc://<host>/<app>`. - **API:** `/api/v1/features` adds `srt`, `rtsp`, `gb28181`, `rtc`, `simulator` and `sanitizer`. The `/api/v1/` index lists only the URLs SRS serves. - **Env support:** - The vhost security rules. - The edge protocol. - The origin cluster and coworkers. - The DVR apply filter. - **Timers in seconds:** `hls_dispose`, `dash_dispose`, `dvr_duration`, `queue_length`, `stun_timeout` and the heartbeat `interval` now accept fractions such as `0.5`. A fraction used to be cut to whole seconds, so `0.5` read as 0. - **Removed features:** a config or env var that names one of these, on or off, now makes SRS refuse to start (and `-t` fail) with `<name> is removed`: - HDS, including `--hds=on`. - The FLV POST caster and `asprocess`. - `query_latest_version` and `first_wait_for_qlv`. - The vhost `bandcheck`, `refer` and `atc_auto`. - Edge `token_traverse`. Check clients with the edge's HTTP hooks instead. ## Tests, CI and skills - **CI:** a new `integration-test` job builds `trunk/Dockerfile.test` (now with `--simulator=on`) and runs srs-integ. - It caches the test media. - It runs the main cases and the timer cases as separate steps, each retried on its own. - Limits are scaled by 4 with `-integ.limit-scale=4`. - **Unit tests:** more stable. They use random seeds, the test servers no longer share ports or leak fds, and a gtest listener fails a test that leaves a handler on a global resource manager. - **Skills:** - The `srs-develop` Gate and Full tiers run `make integ`. - `integration-tests.md` explains how to add cases. - The new `internal-audit-for-srs` skill audits each commit blind before review. --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Co-authored-by: chatgpt-codex-connector[bot] <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com>
340 lines
13 KiB
Go
340 lines
13 KiB
Go
// Copyright (c) 2026 Winlin
|
|
//
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package cases
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"srs-integ/internal/check"
|
|
"srs-integ/internal/media"
|
|
"srs-integ/internal/mock"
|
|
"srs-integ/internal/rtmp"
|
|
"srs-integ/internal/server"
|
|
"srs-integ/internal/srt"
|
|
)
|
|
|
|
// The clients of the security cases are the hook cases' clients: each
|
|
// publishes or plays and reports whether SRS accepts or refuses it. Every
|
|
// client of a case connects from 127.0.0.1, or from ::1 over IPv6.
|
|
|
|
// securityConfig is a server with a listener for every protocol, HLS, an
|
|
// HTTP-FLV mount, the hook mock that accepts every client, and the security
|
|
// rules s.
|
|
func securityConfig(s server.Security) server.Config {
|
|
cfg := server.Config{
|
|
Listeners: []server.Listener{server.RTMP, server.HTTPAPI, server.HTTPServer, server.RTMPS},
|
|
Hooks: true,
|
|
IPv6: true,
|
|
Env: map[string]string{
|
|
"SRS_VHOST_MIN_LATENCY": "on",
|
|
"SRS_VHOST_PLAY_MW_LATENCY": "0",
|
|
"SRS_VHOST_HTTP_REMUX_ENABLED": "on",
|
|
"SRS_VHOST_HTTP_REMUX_MOUNT": "[vhost]/[app]/[stream].flv",
|
|
"SRS_VHOST_HLS_ENABLED": "on",
|
|
},
|
|
}
|
|
cfg = server.WithRTC(cfg, server.RTC{})
|
|
cfg = server.WithSRT(cfg, server.SRTOptions{})
|
|
cfg = server.WithRTSP(cfg, server.RTSPOptions{})
|
|
cfg = server.WithGB(cfg, server.GBOptions{})
|
|
return server.WithSecurity(cfg, s)
|
|
}
|
|
|
|
// securityPublishers are the publishers of every protocol that publishes.
|
|
func securityPublishers() []hookTarget {
|
|
return []hookTarget{
|
|
rtmpHookTarget("RtmpPublish", "on_publish", false, true),
|
|
rtmpHookTarget("RtmpsPublish", "on_publish", true, true),
|
|
srtPublishTarget("SrtPublish", "on_publish"),
|
|
rtcHookTarget(true),
|
|
gbHookTarget(),
|
|
}
|
|
}
|
|
|
|
// securityPlayers are the players of every protocol, each with a publisher
|
|
// of the stream it plays, so a player SRS refuses is refused for itself, not
|
|
// for a stream that isn't there.
|
|
func securityPlayers() []hookTarget {
|
|
rtmpPrepare := func(c *check.C, srs *server.Server, stream string) {
|
|
startPublisher(c, rtmpURL(srs, "live/"+stream), media.Path(c, media.H264HighAAC), 200*time.Millisecond)
|
|
waitHook(c, srs, "on_publish", stream)
|
|
}
|
|
whep := rtcHookTarget(false)
|
|
whep.prepare = func(c *check.C, srs *server.Server, stream string) {
|
|
sendRTC(c, rtcPublisher(c, whipURL(srs, "live", stream), nil))
|
|
waitHook(c, srs, "on_publish", stream)
|
|
}
|
|
rtsp := rtspHookTarget()
|
|
rtsp.prepare = func(c *check.C, srs *server.Server, stream string) {
|
|
rtspPublisher(c, srs, "live/"+stream, media.H264BaselineAAC)
|
|
waitHook(c, srs, "on_publish", stream)
|
|
}
|
|
rtmpPlay := rtmpHookTarget("RtmpPlay", "on_play", false, false)
|
|
rtmpPlay.prepare = rtmpPrepare
|
|
rtmpsPlay := rtmpHookTarget("RtmpsPlay", "on_play", true, false)
|
|
rtmpsPlay.prepare = rtmpPrepare
|
|
return []hookTarget{rtmpPlay, rtmpsPlay, srtPlayTarget(), whep, httpHookTarget(flvHookMount), hlsHookTarget(), rtsp}
|
|
}
|
|
|
|
// securityCase publishes or plays a stream of the case's own: SRS accepts
|
|
// the client when allowed, and else refuses it, with the refusal of the
|
|
// protocol when want names one, and goes on. A publisher SRS refuses leaves
|
|
// the stream unpublished.
|
|
func securityCase(c *check.C, srs *server.Server, target hookTarget, allowed bool, want string) {
|
|
name := streamName(c)
|
|
if target.prepare != nil {
|
|
target.prepare(c, srs, name)
|
|
}
|
|
r := target.try(c, srs, name, hookWait())
|
|
if allowed {
|
|
c.Check(r.accepted, "%s, want accepted", r)
|
|
} else {
|
|
checkRefused(c, r, want)
|
|
if target.hook == "on_publish" {
|
|
c.Check(!streamPublishing(c, srs, "live/"+name), "the stream is published, want it not")
|
|
}
|
|
}
|
|
checkVersions(c, srs)
|
|
}
|
|
|
|
// securityRefusals are how SRS refuses a client the security denies, by the
|
|
// client's name: RTMP and SRT by a close, after the SRT handshake, WHIP and
|
|
// WHEP with 401, and RTSP, HTTP-FLV and HLS with 403; a GB28181 stream is
|
|
// never published.
|
|
var securityRefusals = map[string]string{
|
|
"RtmpPublish": "closed", "RtmpsPublish": "closed", "SrtPublish": "closed", "WhipPublish": "status 401",
|
|
"GbPublish": "not published", "RtmpPlay": "closed", "RtmpsPlay": "closed", "SrtPlay": "closed",
|
|
"WhepPlay": "status 401", "HttpFlvPlay": "status 403", "HlsPlay": "status 403", "RtspPlay": "status 403",
|
|
}
|
|
|
|
// TestSecurity_DenyPublish denies publishing from 127.0.0.1 and allows it
|
|
// from all: a deny rule wins over an allow rule, so every publisher on IPv4
|
|
// is refused, and one on IPv6 is not. Playing is allowed from all.
|
|
func TestSecurity_DenyPublish(t *testing.T) {
|
|
t.Parallel()
|
|
srs := server.Group(t, securityConfig(server.Security{
|
|
AllowPublish: []string{"all"}, DenyPublish: []string{"127.0.0.1"}, AllowPlay: []string{"all"},
|
|
}))
|
|
|
|
for _, target := range securityPublishers() {
|
|
check.Case(t, target.name+"Denied", check.Normal, func(c *check.C) {
|
|
securityCase(c, srs, target, false, securityRefusals[target.name])
|
|
})
|
|
}
|
|
|
|
// RtmpPublishIpv6Allowed publishes over ::1, which the deny rule doesn't
|
|
// name, so allow publish all allows it, and a player on 127.0.0.1 plays
|
|
// it.
|
|
check.Case(t, "RtmpPublishIpv6Allowed", check.Normal, func(c *check.C) {
|
|
if !server.HasIPv6() {
|
|
c.Skip("the host has no IPv6 loopback")
|
|
}
|
|
path := "live/" + streamName(c)
|
|
startPublisher(c, "rtmp://[::1]:"+port(srs)+"/"+path, media.Path(c, media.H264HighAAC), 200*time.Millisecond)
|
|
playChecked(c, rtmpURL(srs, path))
|
|
})
|
|
|
|
// RtmpPlayAllowed plays a stream before its publish: allow play all lets
|
|
// the player in, and it waits for the stream.
|
|
check.Case(t, "RtmpPlayAllowed", check.Normal, func(c *check.C) {
|
|
securityCase(c, srs, rtmpHookTarget("RtmpPlay", "on_play", false, false), true, "")
|
|
})
|
|
}
|
|
|
|
// TestSecurity_DenyPlay allows publishing from 127.0.0.0/8, a CIDR, and
|
|
// denies playing from all: every publisher is accepted, and every player
|
|
// refused, though its stream is published.
|
|
func TestSecurity_DenyPlay(t *testing.T) {
|
|
t.Parallel()
|
|
srs := server.Group(t, securityConfig(server.Security{
|
|
AllowPublish: []string{"127.0.0.0/8"}, DenyPlay: []string{"all"},
|
|
}))
|
|
|
|
for _, target := range securityPublishers() {
|
|
check.Case(t, target.name+"Allowed", check.Normal, func(c *check.C) {
|
|
securityCase(c, srs, target, true, "")
|
|
})
|
|
}
|
|
for _, target := range securityPlayers() {
|
|
check.Case(t, target.name+"Denied", check.Normal, func(c *check.C) {
|
|
securityCase(c, srs, target, false, securityRefusals[target.name])
|
|
})
|
|
}
|
|
|
|
// The DeniedAnswer cases check that an HTTP player the security denies
|
|
// is answered with a client error, as RTSP answers 403 and WHIP and WHEP
|
|
// 401: a refused client is not a server fault, and an empty reply tells
|
|
// the player nothing.
|
|
for _, target := range securityPlayers() {
|
|
if !securityAnswers[target.name] {
|
|
continue
|
|
}
|
|
check.Case(t, target.name+"DeniedAnswer", check.Normal, func(c *check.C) {
|
|
name := streamName(c)
|
|
target.prepare(c, srs, name)
|
|
r := target.try(c, srs, name, hookWait())
|
|
c.Check(strings.HasPrefix(r.refusal, "status 4"), "%s, want a status 4xx", r)
|
|
})
|
|
}
|
|
}
|
|
|
|
// securityAnswers are the HTTP players whose answer to a security deny has a
|
|
// case of its own, by name.
|
|
var securityAnswers = map[string]bool{
|
|
"WhepPlay": true,
|
|
"HttpFlvPlay": true,
|
|
"HlsPlay": true,
|
|
}
|
|
|
|
// TestSecurity_AllowOthers allows publishing only from 10.0.0.0/8 and
|
|
// playing only from 127.0.0.1: a publisher on 127.0.0.1, which no allow rule
|
|
// matches, is refused, and a player there, which an IP names, is let in.
|
|
func TestSecurity_AllowOthers(t *testing.T) {
|
|
t.Parallel()
|
|
srs := server.Group(t, securityConfig(server.Security{
|
|
AllowPublish: []string{"10.0.0.0/8"}, AllowPlay: []string{"127.0.0.1"},
|
|
}))
|
|
|
|
for _, target := range securityPublishers() {
|
|
check.Case(t, target.name+"NotAllowed", check.Normal, func(c *check.C) {
|
|
securityCase(c, srs, target, false, securityRefusals[target.name])
|
|
})
|
|
}
|
|
for _, target := range []hookTarget{
|
|
rtmpHookTarget("RtmpPlay", "on_play", false, false),
|
|
rtmpHookTarget("RtmpsPlay", "on_play", true, false),
|
|
} {
|
|
check.Case(t, target.name+"Allowed", check.Normal, func(c *check.C) {
|
|
securityCase(c, srs, target, true, "")
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestSecurity_NoRules turns the security on with no rule: SRS denies every
|
|
// client, a publisher and a player alike.
|
|
func TestSecurity_NoRules(t *testing.T) {
|
|
t.Parallel()
|
|
srs := server.Group(t, server.WithSecurity(server.Config{
|
|
Listeners: []server.Listener{server.RTMP, server.HTTPAPI},
|
|
}, server.Security{}))
|
|
|
|
check.Case(t, "RtmpPublishDenied", check.Normal, func(c *check.C) {
|
|
securityCase(c, srs, rtmpHookTarget("RtmpPublish", "", false, true), false, "closed")
|
|
})
|
|
check.Case(t, "RtmpPlayDenied", check.Normal, func(c *check.C) {
|
|
securityCase(c, srs, rtmpHookTarget("RtmpPlay", "", false, false), false, "closed")
|
|
})
|
|
}
|
|
|
|
// A securityPublisher publishes a stream over one protocol: first holds it,
|
|
// and second tries to publish it while another protocol holds it.
|
|
type securityPublisher struct {
|
|
name string
|
|
first func(c *check.C, srs *server.Server, stream string)
|
|
second func(c *check.C, srs *server.Server, stream, firstID string)
|
|
}
|
|
|
|
// securitySecondPublishers are the publishers of every protocol that
|
|
// publishes, for the second-publisher cases on Common.
|
|
var securitySecondPublishers = []securityPublisher{
|
|
{
|
|
name: "Rtmp",
|
|
first: func(c *check.C, srs *server.Server, stream string) {
|
|
startPublisher(c, rtmpURL(srs, "live/"+stream), media.Path(c, media.H264HighAAC), 200*time.Millisecond)
|
|
},
|
|
second: func(c *check.C, srs *server.Server, stream, _ string) {
|
|
checkSecondRejected(c, rtmpURL(srs, "live/"+stream), nil)
|
|
},
|
|
},
|
|
{
|
|
name: "Rtmps",
|
|
first: func(c *check.C, srs *server.Server, stream string) {
|
|
startPublisherWith(c, rtmpsURL(srs, "live/"+stream), media.Path(c, media.H264HighAAC), 200*time.Millisecond, tlsOpts(c), rtmp.FMLE)
|
|
},
|
|
second: func(c *check.C, srs *server.Server, stream, _ string) {
|
|
checkSecondRejected(c, rtmpsURL(srs, "live/"+stream), tlsOpts(c))
|
|
},
|
|
},
|
|
{
|
|
name: "Whip",
|
|
first: func(c *check.C, srs *server.Server, stream string) {
|
|
sendRTC(c, rtcPublisher(c, whipURL(srs, "live", stream), nil))
|
|
},
|
|
second: func(c *check.C, srs *server.Server, stream, _ string) {
|
|
offer := rtcOffer(c, true, []string{"H264"}, []string{"opus"})
|
|
rtcRefused(c, whipURL(srs, "live", stream), offer, http.StatusConflict)
|
|
},
|
|
},
|
|
{
|
|
name: "Srt",
|
|
first: func(c *check.C, srs *server.Server, stream string) {
|
|
startSRTPublisher(c, srs, srt.Options{StreamID: srtID("live/"+stream, "publish")}, media.H264HighAACTS)
|
|
},
|
|
second: func(c *check.C, srs *server.Server, stream, _ string) {
|
|
checkSRTClosedEarly(c, dialSRTEarlyClose(c, srs, srt.Options{StreamID: srtID("live/"+stream, "publish")}))
|
|
},
|
|
},
|
|
{
|
|
name: "Gb",
|
|
first: func(c *check.C, srs *server.Server, stream string) {
|
|
gbStartID(c, srs, media.H264BaselineAAC, stream, gbSSRC()).lead(c)
|
|
},
|
|
// The device sees nothing: SRS connects the RTMP client it publishes
|
|
// the stream with, refuses it as busy before its on_publish, and it
|
|
// closes.
|
|
second: func(c *check.C, srs *server.Server, stream, firstID string) {
|
|
gbStartID(c, srs, media.H264BaselineAAC, stream, gbSSRC()).lead(c)
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
|
defer cancel()
|
|
conn, err := srs.Hooks.Wait(ctx, mock.Match{Action: "on_connect", Stream: stream, Func: func(r mock.Request) bool {
|
|
return r.Field("client_id") != firstID
|
|
}})
|
|
c.Must(err, "the on_connect of the GB28181 stream's RTMP client")
|
|
id := conn.Field("client_id")
|
|
_, err = srs.Hooks.Wait(ctx, mock.Match{Action: "on_close", Func: func(r mock.Request) bool { return r.Field("client_id") == id }})
|
|
c.Must(err, "the on_close of the GB28181 stream's RTMP client")
|
|
c.CheckEqual(len(srs.Hooks.Requests(mock.Match{Action: "on_publish", Stream: stream})), 1, "on_publish requests, the first's")
|
|
},
|
|
},
|
|
}
|
|
|
|
// securitySecond adds to TestSecurity_Common a case for each pair of
|
|
// publishers of different protocols: the first publishes a stream, and SRS
|
|
// refuses the second, with the second protocol's refusal, while the first
|
|
// stays the stream's publisher.
|
|
func securitySecond(t *testing.T) {
|
|
for _, first := range securitySecondPublishers {
|
|
for _, second := range securitySecondPublishers {
|
|
if first.name == second.name {
|
|
continue
|
|
}
|
|
check.Case(t, fmt.Sprintf("%sThen%sRejected", first.name, second.name), check.Normal, func(c *check.C) {
|
|
srs := server.Common(c)
|
|
stream := streamName(c)
|
|
first.first(c, srs, stream)
|
|
pub := waitHook(c, srs, "on_publish", stream)
|
|
second.second(c, srs, stream, pub.Field("client_id"))
|
|
st := apiStreamOf(c, srs, pub.Field("stream_id"))
|
|
p := object(c, st, "publish")
|
|
c.CheckEqual(p["active"], true, "the stream is published")
|
|
c.CheckEqual(p["cid"], pub.Field("client_id"), "the stream's publisher, the first")
|
|
checkVersions(c, srs)
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestSecurity_Common has the security cases on the Common server, whose
|
|
// security is off.
|
|
func TestSecurity_Common(t *testing.T) {
|
|
t.Parallel()
|
|
securitySecond(t)
|
|
}
|