Skip to content

Commit 2a10a4a

Browse files
[SoundCloud] Refactor SoundcloudStreamExtractorTest to define test from Immutables test case object
Add Immutables annotation processor to gradle build Add custom Immutables Style Add ISoundcloudStreamExtractorTestCase for use in stream extractor tests Refactor testAudioStreams to match mp3 cdn url by regex
1 parent 91b07d1 commit 2a10a4a

11 files changed

Lines changed: 484 additions & 89 deletions

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ allprojects {
3131
jsr305Version = "3.0.2"
3232
junitVersion = "5.13.3"
3333
checkstyleVersion = "10.4"
34+
immutablesVersion = "2.10.1"
3435
}
3536
}
3637

extractor/build.gradle

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,10 @@ dependencies {
4646

4747
testImplementation "com.squareup.okhttp3:okhttp:4.12.0"
4848
testImplementation 'com.google.code.gson:gson:2.13.1'
49+
testImplementation "org.immutables:value:$immutablesVersion"
50+
testAnnotationProcessor "org.immutables:value:$immutablesVersion"
51+
52+
}
53+
repositories {
54+
mavenCentral()
4955
}

extractor/src/main/java/org/schabi/newpipe/extractor/utils/Parser.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ public static String matchGroup1(final Pattern pattern,
9999
final String input) throws RegexException {
100100
return matchGroup(pattern, input, 1);
101101
}
102-
102+
103103
/**
104104
* Matches the specified group of the given pattern against the input.
105105
*
@@ -115,7 +115,7 @@ public static String matchGroup(final String pattern,
115115
final int group) throws RegexException {
116116
return matchGroup(Pattern.compile(pattern), input, group);
117117
}
118-
118+
119119
@Nonnull
120120
public static String matchGroup(@Nonnull final Pattern pattern,
121121
final String input,
@@ -129,7 +129,7 @@ public static String matchGroup1MultiplePatterns(final Pattern[] patterns, final
129129
}
130130

131131
/**
132-
* Matches multiple patterns against the input string and
132+
* Matches multiple patterns against the input string and
133133
* returns the first successful matcher
134134
*
135135
* @param patterns The array of regex patterns to match.

extractor/src/test/java/org/schabi/newpipe/extractor/ExtractorAsserts.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import java.util.Collections;
99
import java.util.List;
1010
import java.util.Set;
11+
import java.util.regex.Pattern;
1112
import java.util.stream.Collectors;
1213

1314
import javax.annotation.Nonnull;
@@ -168,6 +169,13 @@ public static void assertContains(
168169
"'" + shouldBeContained + "' should be contained inside '" + container + "'");
169170
}
170171

172+
public static void assertMatches(final Pattern pattern, final String input) {
173+
assertNotNull(pattern, "pattern is null");
174+
assertNotNull(input, "input is null");
175+
assertTrue(pattern.matcher(input).find(),
176+
"Pattern '" + pattern + "' not found in input '" + input + "'");
177+
}
178+
171179
public static void assertTabsContain(@Nonnull final List<ListLinkHandler> tabs,
172180
@Nonnull final String... expectedTabs) {
173181
final Set<String> tabSet = tabs.stream()
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package org.schabi.newpipe.extractor;
2+
3+
import org.immutables.value.Value;
4+
5+
import java.lang.annotation.ElementType;
6+
import java.lang.annotation.Target;
7+
8+
// CHECKSTYLE:OFF
9+
/**
10+
* Custom style for generated Immutables.
11+
* See <a href="https://immutables.github.io/style.html">Style</a>.
12+
* <p>
13+
* - Abstract types start with 'I' (e.g., IExample).<p>
14+
* - Concrete immutable types do not have a prefix (e.g., Example).<p>
15+
* - Getters are prefixed with 'get', 'is', or no prefix.<p>
16+
* - <a href="https://immutables.github.io/immutable.html#strict-builder">Strict builder pattern is enforced.</a><p>
17+
*/
18+
// CHECKSTYLE:ON
19+
@Target({ElementType.PACKAGE, ElementType.TYPE})
20+
@Value.Style(
21+
get = {"get*", "is*", "*"}, // Methods matching these prefixes will be used as getters.
22+
// Methods matching these patterns can NOT be used as setters.
23+
typeAbstract = {"I*"}, // Abstract types start with I
24+
typeImmutable = "*", // Generated concrete Immutable types will not have the I prefix
25+
visibility = Value.Style.ImplementationVisibility.PUBLIC,
26+
strictBuilder = true,
27+
defaultAsDefault = true, // https://immutables.github.io/immutable.html#default-attributes
28+
jdkOnly = true
29+
)
30+
public @interface ImmutableStyle { }
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package org.schabi.newpipe.extractor.services;
2+
3+
import org.junit.jupiter.api.Test;
4+
import org.schabi.newpipe.extractor.ExtractorAsserts;
5+
import org.schabi.newpipe.extractor.MediaFormat;
6+
import org.schabi.newpipe.extractor.services.testcases.SoundcloudStreamExtractorTestCase;
7+
import org.schabi.newpipe.extractor.stream.AudioStream;
8+
import org.schabi.newpipe.extractor.stream.DeliveryMethod;
9+
10+
import java.util.List;
11+
import java.util.regex.Pattern;
12+
13+
import static org.junit.jupiter.api.Assertions.*;
14+
15+
public abstract class ParameterisedDefaultSoundcloudStreamExtractorTest
16+
extends ParameterisedDefaultStreamExtractorTest<SoundcloudStreamExtractorTestCase> {
17+
protected ParameterisedDefaultSoundcloudStreamExtractorTest(SoundcloudStreamExtractorTestCase testCase) {
18+
super(testCase);
19+
}
20+
21+
final Pattern mp3CdnUrlPattern = Pattern.compile("-media\\.sndcdn\\.com/[a-zA-Z0-9]{12}\\.128\\.mp3");
22+
23+
@Override
24+
@Test
25+
public void testAudioStreams() throws Exception {
26+
super.testAudioStreams();
27+
final List<AudioStream> audioStreams = extractor.getAudioStreams();
28+
assertEquals(3, audioStreams.size()); // 2 MP3 streams (1 progressive, 1 HLS) and 1 OPUS
29+
audioStreams.forEach(audioStream -> {
30+
final DeliveryMethod deliveryMethod = audioStream.getDeliveryMethod();
31+
final String mediaUrl = audioStream.getContent();
32+
if (audioStream.getFormat() == MediaFormat.OPUS) {
33+
assertSame(DeliveryMethod.HLS, deliveryMethod,
34+
"Wrong delivery method for stream " + audioStream.getId() + ": "
35+
+ deliveryMethod);
36+
// Assert it's an OPUS 64 kbps media playlist URL which comes from an HLS
37+
// SoundCloud CDN
38+
ExtractorAsserts.assertContains("-hls-opus-media.sndcdn.com", mediaUrl);
39+
ExtractorAsserts.assertContains(".64.opus", mediaUrl);
40+
} else if (audioStream.getFormat() == MediaFormat.MP3) {
41+
if (deliveryMethod == DeliveryMethod.PROGRESSIVE_HTTP) {
42+
// Assert it's a MP3 128 kbps media URL which comes from a progressive
43+
// SoundCloud CDN
44+
ExtractorAsserts.assertMatches(mp3CdnUrlPattern, mediaUrl);
45+
} else if (deliveryMethod == DeliveryMethod.HLS) {
46+
// Assert it's a MP3 128 kbps media HLS playlist URL which comes from an HLS
47+
// SoundCloud CDN
48+
ExtractorAsserts.assertContains("-hls-media.sndcdn.com", mediaUrl);
49+
ExtractorAsserts.assertContains(".128.mp3", mediaUrl);
50+
} else {
51+
fail("Wrong delivery method for stream " + audioStream.getId() + ": "
52+
+ deliveryMethod);
53+
}
54+
}
55+
});
56+
}
57+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package org.schabi.newpipe.extractor.services;
2+
3+
import org.junit.jupiter.api.BeforeAll;
4+
import org.junit.jupiter.api.TestInstance;
5+
import org.schabi.newpipe.downloader.DownloaderTestImpl;
6+
import org.schabi.newpipe.extractor.MetaInfo;
7+
import org.schabi.newpipe.extractor.NewPipe;
8+
import org.schabi.newpipe.extractor.StreamingService;
9+
import org.schabi.newpipe.extractor.services.testcases.DefaultStreamExtractorTestCase;
10+
import org.schabi.newpipe.extractor.stream.StreamExtractor;
11+
import org.schabi.newpipe.extractor.stream.StreamType;
12+
13+
import javax.annotation.Nullable;
14+
import java.util.List;
15+
import java.util.Locale;
16+
17+
/**
18+
* Test for {@link StreamExtractor}
19+
*/
20+
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
21+
public abstract class ParameterisedDefaultStreamExtractorTest<TTestCase extends DefaultStreamExtractorTestCase> extends DefaultStreamExtractorTest {
22+
protected TTestCase testCase;
23+
protected StreamExtractor extractor;
24+
25+
protected ParameterisedDefaultStreamExtractorTest(TTestCase testCase)
26+
{
27+
this.testCase = testCase;
28+
}
29+
30+
@BeforeAll
31+
public void setUp() throws Exception {
32+
if (extractor != null) {
33+
throw new IllegalStateException("extractor already initialized before BeforeAll");
34+
}
35+
NewPipe.init(DownloaderTestImpl.getInstance());
36+
extractor = testCase.service().getStreamExtractor(testCase.url());
37+
extractor.fetchPage();
38+
}
39+
40+
///
41+
/// DefaultExtractorTest overrides
42+
///
43+
44+
@Override public StreamExtractor extractor() throws Exception { return extractor; }
45+
46+
@Override public StreamingService expectedService() throws Exception { return testCase.service(); }
47+
@Override public String expectedName() throws Exception { return testCase.name(); }
48+
@Override public String expectedId() throws Exception { return testCase.id(); }
49+
@Override public String expectedUrlContains() throws Exception { return testCase.urlContains(); }
50+
@Override public String expectedOriginalUrlContains() throws Exception { return testCase.originalUrlContains(); }
51+
52+
///
53+
/// DefaultStreamExtractorTest overrides
54+
///
55+
@Override public StreamType expectedStreamType() { return testCase.streamType(); }
56+
@Override public String expectedUploaderName() { return testCase.uploaderName(); }
57+
@Override public String expectedUploaderUrl() { return testCase.uploaderUrl(); }
58+
@Override public boolean expectedUploaderVerified() { return testCase.uploaderVerified(); }
59+
@Override public long expectedUploaderSubscriberCountAtLeast() { return testCase.uploaderSubscriberCountAtLeast(); }
60+
@Override public String expectedSubChannelName() { return testCase.subChannelName(); }
61+
@Override public String expectedSubChannelUrl() { return testCase.subChannelUrl(); }
62+
@Override public boolean expectedDescriptionIsEmpty() { return testCase.descriptionIsEmpty(); }
63+
@Override public List<String> expectedDescriptionContains() { return testCase.descriptionContains(); }
64+
@Override public long expectedLength() { return testCase.length(); }
65+
@Override public long expectedTimestamp() { return testCase.timestamp(); }
66+
@Override public long expectedViewCountAtLeast() { return testCase.viewCountAtLeast(); }
67+
@Override @Nullable public String expectedUploadDate() { return testCase.uploadDate(); }
68+
@Override @Nullable public String expectedTextualUploadDate() { return testCase.textualUploadDate(); }
69+
@Override public long expectedLikeCountAtLeast() { return testCase.likeCountAtLeast(); }
70+
@Override public long expectedDislikeCountAtLeast() { return testCase.dislikeCountAtLeast(); }
71+
@Override public boolean expectedHasRelatedItems() { return testCase.hasRelatedItems(); }
72+
@Override public int expectedAgeLimit() { return testCase.ageLimit(); }
73+
@Override @Nullable public String expectedErrorMessage() { return testCase.errorMessage(); }
74+
@Override public boolean expectedHasVideoStreams() { return testCase.hasVideoStreams(); }
75+
@Override public boolean expectedHasAudioStreams() { return testCase.hasAudioStreams(); }
76+
@Override public boolean expectedHasSubtitles() { return testCase.hasSubtitles(); }
77+
@Override @Nullable public String expectedDashMpdUrlContains() { return testCase.dashMpdUrlContains(); }
78+
@Override public boolean expectedHasFrames() { return testCase.hasFrames(); }
79+
@Override public String expectedHost() { return testCase.host(); }
80+
@Override public StreamExtractor.Privacy expectedPrivacy() { return testCase.privacy(); }
81+
@Override public String expectedCategory() { return testCase.category(); }
82+
@Override public String expectedLicence() { return testCase.licence(); }
83+
@Override public Locale expectedLanguageInfo() { return testCase.languageInfo(); }
84+
@Override public List<String> expectedTags() { return testCase.tags(); }
85+
@Override public String expectedSupportInfo() { return testCase.supportInfo(); }
86+
@Override public int expectedStreamSegmentsCount() { return testCase.streamSegmentsCount(); }
87+
@Override public List<MetaInfo> expectedMetaInfo() { return testCase.metaInfo(); }
88+
}

0 commit comments

Comments
 (0)