diff --git a/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeDashManifestCreatorTest.java b/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeDashManifestCreatorTest.java new file mode 100644 index 000000000..a644272d1 --- /dev/null +++ b/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeDashManifestCreatorTest.java @@ -0,0 +1,620 @@ +package org.schabi.newpipe.extractor.services.youtube; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.schabi.newpipe.downloader.DownloaderTestImpl; +import org.schabi.newpipe.extractor.NewPipe; +import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExtractor; +import org.schabi.newpipe.extractor.stream.DeliveryMethod; +import org.schabi.newpipe.extractor.stream.Stream; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; + +import javax.annotation.Nonnull; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import java.io.StringReader; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.List; +import java.util.Random; + +import static org.junit.jupiter.api.Assertions.*; +import static org.schabi.newpipe.extractor.ServiceList.YouTube; +import static org.schabi.newpipe.extractor.utils.Utils.isBlank; + +/** + * Test for {@link YoutubeDashManifestCreator}. + * + *

+ * We cannot test the generation of DASH manifests for ended livestreams because these videos will + * be re-encoded as normal videos later, so we can't use a specific video. + *

+ * + *

+ * The generation of DASH manifests for OTF streams, which can be tested, uses a video licenced + * under the Creative Commons Attribution licence (reuse allowed): + * {@code https://www.youtube.com/watch?v=DJ8GQUNUXGM} + *

+ * + *

+ * We couldn't use mocks for these tests because the streaming URLs needs to fetched and the IP + * address used to get these URLs is required (used as a param in the URLs; without it, video + * servers return 403/Forbidden HTTP response code). + *

+ * + *

+ * So the real downloader will be used everytime on this test class. + *

+ */ +class YoutubeDashManifestCreatorTest { + // Setting a higher number may let Google video servers return a lot of 403s + private static final int MAXIMUM_NUMBER_OF_STREAMS_TO_TEST = 3; + + public static class testGenerationOfOtfAndProgressiveManifests { + private static final String url = "https://www.youtube.com/watch?v=DJ8GQUNUXGM"; + private static YoutubeStreamExtractor extractor; + + @BeforeAll + public static void setUp() throws Exception { + YoutubeParsingHelper.resetClientVersionAndKey(); + YoutubeParsingHelper.setNumberGenerator(new Random(1)); + NewPipe.init(DownloaderTestImpl.getInstance()); + extractor = (YoutubeStreamExtractor) YouTube.getStreamExtractor(url); + extractor.fetchPage(); + } + + @Test + void testOtfStreamsANewEraOfOpen() throws Exception { + testStreams(DeliveryMethod.DASH, + extractor.getVideoOnlyStreams()); + testStreams(DeliveryMethod.DASH, + extractor.getAudioStreams()); + // This should not happen because there are no video stream with audio which use the + // DASH delivery method (YouTube OTF stream type) + try { + testStreams(DeliveryMethod.DASH, + extractor.getVideoStreams()); + } catch (final Exception e) { + assertEquals(YoutubeDashManifestCreator.YoutubeDashManifestCreationException.class, + e.getClass(), "The exception thrown was not the one excepted: " + + e.getClass().getName() + + "was thrown instead of YoutubeDashManifestCreationException"); + } + } + + @Test + void testProgressiveStreamsANewEraOfOpen() throws Exception { + testStreams(DeliveryMethod.PROGRESSIVE_HTTP, + extractor.getVideoOnlyStreams()); + testStreams(DeliveryMethod.PROGRESSIVE_HTTP, + extractor.getAudioStreams()); + try { + testStreams(DeliveryMethod.PROGRESSIVE_HTTP, + extractor.getVideoStreams()); + } catch (final Exception e) { + assertEquals(YoutubeDashManifestCreator.YoutubeDashManifestCreationException.class, + e.getClass(), "The exception thrown was not the one excepted: " + + e.getClass().getName() + + "was thrown instead of YoutubeDashManifestCreationException"); + } + } + + private void testStreams(@Nonnull final DeliveryMethod deliveryMethodToTest, + @Nonnull final List streamList) + throws Exception { + int i = 0; + final int streamListSize = streamList.size(); + final boolean isDeliveryMethodToTestProgressiveHttpDeliveryMethod = + deliveryMethodToTest == DeliveryMethod.PROGRESSIVE_HTTP; + final long videoLength = extractor.getLength(); + + // Test at most the first five streams we found + while (i <= YoutubeDashManifestCreatorTest.MAXIMUM_NUMBER_OF_STREAMS_TO_TEST + && i < streamListSize) { + final Stream stream = streamList.get(i); + if (stream.getDeliveryMethod() == deliveryMethodToTest) { + final String baseUrl = stream.getContent(); + assertFalse(isBlank(baseUrl), "The base URL of the stream is empty"); + + final ItagItem itagItem = stream.getItagItem(); + assertNotNull(itagItem, "The itagItem is null"); + + final String dashManifest; + if (isDeliveryMethodToTestProgressiveHttpDeliveryMethod) { + dashManifest = YoutubeDashManifestCreator + .createDashManifestFromProgressiveStreamingUrl(baseUrl, itagItem, + videoLength); + } else if (deliveryMethodToTest == DeliveryMethod.DASH) { + dashManifest = YoutubeDashManifestCreator + .createDashManifestFromOtfStreamingUrl(baseUrl, itagItem, + videoLength); + } else { + throw new IllegalArgumentException( + "The delivery method provided is not the progressive HTTP or the DASH delivery method"); + } + testManifestGenerated(dashManifest, itagItem, + isDeliveryMethodToTestProgressiveHttpDeliveryMethod); + assertFalse(isBlank(dashManifest), "The DASH manifest is null or empty: " + + dashManifest); + } + i++; + } + } + + private void testManifestGenerated(final String dashManifest, + @Nonnull final ItagItem itagItem, + final boolean isAProgressiveStreamingUrl) + throws Exception { + final DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory + .newInstance(); + final DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); + final Document document = documentBuilder.parse(new InputSource( + new StringReader(dashManifest))); + + testMpdElement(document); + testPeriodElement(document); + testAdaptationSetElement(document, itagItem); + testRoleElement(document); + testRepresentationElement(document, itagItem); + if (itagItem.itagType.equals(ItagItem.ItagType.AUDIO)) { + testAudioChannelConfigurationElement(document, itagItem); + } + if (isAProgressiveStreamingUrl) { + testBaseUrlElement(document); + testSegmentBaseElement(document, itagItem); + testInitializationElement(document, itagItem); + } else { + testSegmentTemplateElement(document); + testSegmentTimelineAndSElements(document); + } + } + + private void testMpdElement(@Nonnull final Document document) { + final Element mpdElement = (Element) document.getElementsByTagName("MPD") + .item(0); + assertNotNull(mpdElement, "The MPD element doesn't exist"); + assertNull(mpdElement.getParentNode().getNodeValue(), "The MPD element has a parent element"); + + final String mediaPresentationDurationValue = mpdElement + .getAttribute("mediaPresentationDuration"); + assertNotNull(mediaPresentationDurationValue, + "The value of the mediaPresentationDuration attribute is empty or the corresponding attribute doesn't exist"); + assertTrue(mediaPresentationDurationValue.startsWith("PT"), + "The mediaPresentationDuration attribute of the DASH manifest is not valid"); + } + + private void testPeriodElement(@Nonnull final Document document) { + final Element periodElement = (Element) document.getElementsByTagName("Period") + .item(0); + assertNotNull(periodElement, "The Period element doesn't exist"); + assertTrue(periodElement.getParentNode().isEqualNode( + document.getElementsByTagName("MPD").item(0)), + "The MPD element doesn't contain a Period element"); + } + + private void testAdaptationSetElement(@Nonnull final Document document, + @Nonnull final ItagItem itagItem) { + final Element adaptationSetElement = (Element) document + .getElementsByTagName("AdaptationSet").item(0); + assertNotNull(adaptationSetElement, "The AdaptationSet element doesn't exist"); + assertTrue(adaptationSetElement.getParentNode().isEqualNode( + document.getElementsByTagName("Period").item(0)), + "The Period element doesn't contain an AdaptationSet element"); + + final String mimeTypeDashManifestValue = adaptationSetElement + .getAttribute("mimeType"); + assertFalse(isBlank(mimeTypeDashManifestValue), + "The value of the mimeType attribute is empty or the corresponding attribute doesn't exist"); + + final String mimeTypeItagItemValue = itagItem.getMediaFormat().getMimeType(); + assertFalse(isBlank(mimeTypeItagItemValue), "The mimeType of the ItagItem is empty"); + + assertEquals(mimeTypeDashManifestValue, mimeTypeItagItemValue, + "The mimeType attribute of the DASH manifest (" + mimeTypeItagItemValue + + ") is not equal to the mimeType set in the ItagItem object (" + + mimeTypeItagItemValue + ")"); + } + + private void testRoleElement(@Nonnull final Document document) { + final Element roleElement = (Element) document.getElementsByTagName("Role") + .item(0); + assertNotNull(roleElement, "The Role element doesn't exist"); + assertTrue(roleElement.getParentNode().isEqualNode( + document.getElementsByTagName("AdaptationSet").item(0)), + "The AdaptationSet element doesn't contain a Role element"); + } + + private void testRepresentationElement(@Nonnull final Document document, + @Nonnull final ItagItem itagItem) { + final Element representationElement = (Element) document + .getElementsByTagName("Representation").item(0); + assertNotNull(representationElement, "The Representation element doesn't exist"); + assertTrue(representationElement.getParentNode().isEqualNode( + document.getElementsByTagName("AdaptationSet").item(0)), + "The AdaptationSet element doesn't contain a Representation element"); + + final String bandwidthDashManifestValue = representationElement + .getAttribute("bandwidth"); + assertFalse(isBlank(bandwidthDashManifestValue), + "The value of the bandwidth attribute is empty or the corresponding attribute doesn't exist"); + + final int bandwidthDashManifest; + try { + bandwidthDashManifest = Integer.parseInt(bandwidthDashManifestValue); + } catch (final NumberFormatException e) { + throw new AssertionError("The value of the bandwidth attribute is not an integer", + e); + } + assertTrue(bandwidthDashManifest > 0, + "The value of the bandwidth attribute is less than or equal to 0"); + + final int bitrateItagItem = itagItem.getBitrate(); + assertTrue(bitrateItagItem > 0, + "The bitrate of the ItagItem is less than or equal to 0"); + + assertEquals(bandwidthDashManifest, bitrateItagItem, + "The value of the bandwidth attribute of the DASH manifest (" + + bandwidthDashManifest + + ") is not equal to the bitrate value set in the ItagItem object (" + + bitrateItagItem + ")"); + + final String codecsDashManifestValue = representationElement.getAttribute("codecs"); + assertFalse(isBlank(codecsDashManifestValue), + "The value of the codecs attribute is empty or the corresponding attribute doesn't exist"); + + final String codecsItagItemValue = itagItem.getCodec(); + assertFalse(isBlank(codecsItagItemValue), "The codec of the ItagItem is empty"); + + assertEquals(codecsDashManifestValue, codecsItagItemValue, + "The value of the codecs attribute of the DASH manifest (" + + codecsDashManifestValue + + ") is not equal to the codecs value set in the ItagItem object (" + + codecsItagItemValue + ")"); + + if (itagItem.itagType == ItagItem.ItagType.VIDEO_ONLY + || itagItem.itagType == ItagItem.ItagType.VIDEO) { + testVideoItagItemAttributes(representationElement, itagItem); + } + + final String idDashManifestValue = representationElement.getAttribute("id"); + assertFalse(isBlank(idDashManifestValue), + "The value of the id attribute is empty or the corresponding attribute doesn't exist"); + + final int idDashManifest; + try { + idDashManifest = Integer.parseInt(idDashManifestValue); + } catch (final NumberFormatException e) { + throw new AssertionError("The value of the id attribute is not an integer", + e); + } + assertTrue(idDashManifest > 0, "The value of the id attribute is less than or equal to 0"); + + final int idItagItem = itagItem.id; + assertTrue(idItagItem > 0, "The id of the ItagItem is less than or equal to 0"); + assertEquals(idDashManifest, idItagItem, + "The value of the id attribute of the DASH manifest (" + idDashManifestValue + + ") is not equal to the id of the ItagItem object (" + idItagItem + + ")"); + } + + private void testVideoItagItemAttributes(@Nonnull final Element representationElement, + @Nonnull final ItagItem itagItem) { + final String frameRateDashManifestValue = representationElement + .getAttribute("frameRate"); + assertFalse(isBlank(frameRateDashManifestValue), + "The value of the frameRate attribute is empty or the corresponding attribute doesn't exist"); + + final int frameRateDashManifest; + try { + frameRateDashManifest = Integer.parseInt(frameRateDashManifestValue); + } catch (final NumberFormatException e) { + throw new AssertionError("The value of the frameRate attribute is not an integer", + e); + } + assertTrue(frameRateDashManifest > 0, + "The value of the frameRate attribute is less than or equal to 0"); + + final int fpsItagItem = itagItem.getFps(); + assertTrue(fpsItagItem > 0, "The fps of the ItagItem is unknown"); + + assertEquals(frameRateDashManifest, fpsItagItem, + "The value of the frameRate attribute of the DASH manifest (" + + frameRateDashManifest + + ") is not equal to the frame rate value set in the ItagItem object (" + + fpsItagItem + ")"); + + final String heightDashManifestValue = representationElement.getAttribute("height"); + assertFalse(isBlank(heightDashManifestValue), + "The value of the height attribute is empty or the corresponding attribute doesn't exist"); + + final int heightDashManifest; + try { + heightDashManifest = Integer.parseInt(heightDashManifestValue); + } catch (final NumberFormatException e) { + throw new AssertionError("The value of the height attribute is not an integer", + e); + } + assertTrue(heightDashManifest > 0, + "The value of the height attribute is less than or equal to 0"); + + final int heightItagItem = itagItem.getHeight(); + assertTrue(heightItagItem > 0, + "The height of the ItagItem is less than or equal to 0"); + + assertEquals(heightDashManifest, heightItagItem, + "The value of the height attribute of the DASH manifest (" + + heightDashManifest + + ") is not equal to the height value set in the ItagItem object (" + + heightItagItem + ")"); + + final String widthDashManifestValue = representationElement.getAttribute("width"); + assertFalse(isBlank(widthDashManifestValue), + "The value of the width attribute is empty or the corresponding attribute doesn't exist"); + + final int widthDashManifest; + try { + widthDashManifest = Integer.parseInt(widthDashManifestValue); + } catch (final NumberFormatException e) { + throw new AssertionError("The value of the width attribute is not an integer", + e); + } + assertTrue(widthDashManifest > 0, + "The value of the width attribute is less than or equal to 0"); + + final int widthItagItem = itagItem.getWidth(); + assertTrue(widthItagItem > 0, "The width of the ItagItem is less than or equal to 0"); + + assertEquals(widthDashManifest, widthItagItem, + "The value of the width attribute of the DASH manifest (" + widthDashManifest + + ") is not equal to the width value set in the ItagItem object (" + + widthItagItem + ")"); + } + + private void testAudioChannelConfigurationElement(@Nonnull final Document document, + @Nonnull final ItagItem itagItem) { + final Element audioChannelConfigurationElement = (Element) document + .getElementsByTagName("AudioChannelConfiguration").item(0); + assertNotNull(audioChannelConfigurationElement, + "The AudioChannelConfiguration element doesn't exist"); + assertTrue(audioChannelConfigurationElement.getParentNode().isEqualNode( + document.getElementsByTagName("Representation").item(0)), + "The Representation element doesn't contain an AudioChannelConfiguration element"); + + final String audioChannelsDashManifestValue = audioChannelConfigurationElement + .getAttribute("value"); + assertFalse(isBlank(audioChannelsDashManifestValue), + "The value of the value attribute is empty or the corresponding attribute doesn't exist"); + + final int audioChannelsDashManifest; + try { + audioChannelsDashManifest = Integer.parseInt(audioChannelsDashManifestValue); + } catch (final NumberFormatException e) { + throw new AssertionError( + "The number of audio channels (the value attribute) is not an integer", + e); + } + assertTrue(audioChannelsDashManifest > 0, + "The number of audio channels (the value attribute) is less than or equal to 0"); + + final int audioChannelsItagItem = itagItem.getAudioChannels(); + assertTrue(audioChannelsItagItem > 0, + "The number of audio channels of the ItagItem is less than or equal to 0"); + + assertEquals(audioChannelsDashManifest, audioChannelsItagItem, + "The value of the value attribute of the DASH manifest (" + + audioChannelsDashManifest + + ") is not equal to the number of audio channels set in the ItagItem object (" + + audioChannelsItagItem + ")"); + } + + private void testSegmentTemplateElement(@Nonnull final Document document) { + final Element segmentTemplateElement = (Element) document + .getElementsByTagName("SegmentTemplate").item(0); + assertNotNull(segmentTemplateElement, "The SegmentTemplate element doesn't exist"); + assertTrue(segmentTemplateElement.getParentNode().isEqualNode( + document.getElementsByTagName("Representation").item(0)), + "The Representation element doesn't contain a SegmentTemplate element"); + + final String initializationValue = segmentTemplateElement + .getAttribute("initialization"); + assertFalse(isBlank(initializationValue), + "The value of the initialization attribute is empty or the corresponding attribute doesn't exist"); + try { + new URL(initializationValue); + } catch (final MalformedURLException e) { + throw new AssertionError("The value of the initialization attribute is not an URL", + e); + } + assertTrue(initializationValue.endsWith("&sq=0&rn=0"), + "The value of the initialization attribute doesn't end with &sq=0&rn=0"); + + final String mediaValue = segmentTemplateElement.getAttribute("media"); + assertFalse(isBlank(mediaValue), + "The value of the media attribute is empty or the corresponding attribute doesn't exist"); + try { + new URL(mediaValue); + } catch (final MalformedURLException e) { + throw new AssertionError("The value of the media attribute is not an URL", + e); + } + assertTrue(mediaValue.endsWith("&sq=$Number$&rn=$Number$"), + "The value of the media attribute doesn't end with &sq=$Number$&rn=$Number$"); + + final String startNumberValue = segmentTemplateElement.getAttribute("startNumber"); + assertFalse(isBlank(startNumberValue), + "The value of the startNumber attribute is empty or the corresponding attribute doesn't exist"); + assertEquals("1", startNumberValue, + "The value of the startNumber attribute is not equal to 1"); + } + + private void testSegmentTimelineAndSElements(@Nonnull final Document document) { + final Element segmentTimelineElement = (Element) document + .getElementsByTagName("SegmentTimeline").item(0); + assertNotNull(segmentTimelineElement, "The SegmentTimeline element doesn't exist"); + assertTrue(segmentTimelineElement.getParentNode().isEqualNode( + document.getElementsByTagName("SegmentTemplate").item(0)), + "The SegmentTemplate element doesn't contain a SegmentTimeline element"); + testSElements(segmentTimelineElement); + } + + private void testSElements(@Nonnull final Element segmentTimelineElement) { + final NodeList segmentTimelineElementChildren = segmentTimelineElement.getChildNodes(); + final int segmentTimelineElementChildrenLength = segmentTimelineElementChildren + .getLength(); + assertNotEquals(0, segmentTimelineElementChildrenLength, + "The DASH manifest doesn't have a segment element (S) in the SegmentTimeLine element"); + + for (int i = 0; i < segmentTimelineElementChildrenLength; i++) { + final Element sElement = (Element) segmentTimelineElement.getElementsByTagName("S") + .item(i); + + final String dValue = sElement.getAttribute("d"); + assertFalse(isBlank(dValue), + "The value of the duration of this segment (the d attribute of this S element) is empty or the corresponding attribute doesn't exist"); + + final int d; + try { + d = Integer.parseInt(dValue); + } catch (final NumberFormatException e) { + throw new AssertionError("The value of the d attribute is not an integer", e); + } + assertTrue(d > 0, "The value of the d attribute is less than or equal to 0"); + + final String rValue = sElement.getAttribute("r"); + // A segment duration can or can't be repeated, so test the next segment if there + // is no r attribute + if (!isBlank(rValue)) { + final int r; + try { + r = Integer.parseInt(dValue); + } catch (final NumberFormatException e) { + throw new AssertionError("The value of the r attribute is not an integer", + e); + } + assertTrue(r > 0, "The value of the r attribute is less than or equal to 0"); + } + } + } + + private void testBaseUrlElement(@Nonnull final Document document) { + final Element baseURLElement = (Element) document + .getElementsByTagName("BaseURL").item(0); + assertNotNull(baseURLElement, "The BaseURL element doesn't exist"); + assertTrue(baseURLElement.getParentNode().isEqualNode( + document.getElementsByTagName("Representation").item(0)), + "The Representation element doesn't contain a BaseURL element"); + + final String baseURLElementContentValue = baseURLElement + .getTextContent(); + assertFalse(isBlank(baseURLElementContentValue), + "The content of the BaseURL element is empty or the corresponding element has no content"); + + try { + new URL(baseURLElementContentValue); + } catch (final MalformedURLException e) { + throw new AssertionError("The content of the BaseURL element is not an URL", e); + } + } + + private void testSegmentBaseElement(@Nonnull final Document document, + @Nonnull final ItagItem itagItem) { + final Element segmentBaseElement = (Element) document + .getElementsByTagName("SegmentBase").item(0); + assertNotNull(segmentBaseElement, "The SegmentBase element doesn't exist"); + assertTrue(segmentBaseElement.getParentNode().isEqualNode( + document.getElementsByTagName("Representation").item(0)), + "The Representation element doesn't contain a SegmentBase element"); + + final String indexRangeValue = segmentBaseElement + .getAttribute("indexRange"); + assertFalse(isBlank(indexRangeValue), + "The value of the indexRange attribute is empty or the corresponding attribute doesn't exist"); + final String[] indexRangeParts = indexRangeValue.split("-"); + assertEquals(2, indexRangeParts.length, + "The value of the indexRange attribute is not valid"); + + final int dashManifestIndexStart; + try { + dashManifestIndexStart = Integer.parseInt(indexRangeParts[0]); + } catch (final NumberFormatException e) { + throw new AssertionError("The value of the indexRange attribute is not valid", e); + } + + final int itagItemIndexStart = itagItem.getIndexStart(); + assertTrue(itagItemIndexStart > 0, + "The indexStart of the ItagItem is less than or equal to 0"); + assertEquals(dashManifestIndexStart, itagItemIndexStart, + "The indexStart value of the indexRange attribute of the DASH manifest (" + + dashManifestIndexStart + + ") is not equal to the indexStart of the ItagItem object (" + + itagItemIndexStart + ")"); + + final int dashManifestIndexEnd; + try { + dashManifestIndexEnd = Integer.parseInt(indexRangeParts[1]); + } catch (final NumberFormatException e) { + throw new AssertionError("The value of the indexRange attribute is not valid", e); + } + + final int itagItemIndexEnd = itagItem.getIndexEnd(); + assertTrue(itagItemIndexEnd > 0, + "The indexEnd of the ItagItem is less than or equal to 0"); + + assertEquals(dashManifestIndexEnd, itagItemIndexEnd, + "The indexEnd value of the indexRange attribute of the DASH manifest (" + + dashManifestIndexEnd + + ") is not equal to the indexEnd of the ItagItem object (" + + itagItemIndexEnd + ")"); + } + + private void testInitializationElement(@Nonnull final Document document, + @Nonnull final ItagItem itagItem) { + final Element initializationElement = (Element) document + .getElementsByTagName("Initialization").item(0); + assertNotNull(initializationElement, "The Initialization element doesn't exist"); + assertTrue(initializationElement.getParentNode().isEqualNode( + document.getElementsByTagName("SegmentBase").item(0)), + "The SegmentBase element doesn't contain an Initialization element"); + + final String rangeValue = initializationElement + .getAttribute("range"); + assertFalse(isBlank(rangeValue), + "The value of the range attribute is empty or the corresponding attribute doesn't exist"); + final String[] rangeParts = rangeValue.split("-"); + assertEquals(2, rangeParts.length, "The value of the range attribute is not valid"); + + final int dashManifestInitStart; + try { + dashManifestInitStart = Integer.parseInt(rangeParts[0]); + } catch (final NumberFormatException e) { + throw new AssertionError("The value of the range attribute is not valid", e); + } + + final int itagItemInitStart = itagItem.getInitStart(); + assertTrue(itagItemInitStart >= 0, "The initStart of the ItagItem is less than 0"); + assertEquals(dashManifestInitStart, itagItemInitStart, + "The initStart value of the range attribute of the DASH manifest (" + + dashManifestInitStart + + ") is not equal to the initStart of the ItagItem object (" + + itagItemInitStart + ")"); + + final int dashManifestInitEnd; + try { + dashManifestInitEnd = Integer.parseInt(rangeParts[1]); + } catch (final NumberFormatException e) { + throw new AssertionError("The value of the indexRange attribute is not valid", e); + } + + final int itagItemInitEnd = itagItem.getInitEnd(); + assertTrue(itagItemInitEnd > 0, "The indexEnd of the ItagItem is less than or equal to 0"); + assertEquals(dashManifestInitEnd, itagItemInitEnd, + "The initEnd value of the range attribute of the DASH manifest (" + + dashManifestInitEnd + + ") is not equal to the initEnd of the ItagItem object (" + + itagItemInitEnd + ")"); + } + } +} diff --git a/extractor/src/test/java/org/schabi/newpipe/extractor/utils/UtilsTest.java b/extractor/src/test/java/org/schabi/newpipe/extractor/utils/UtilsTest.java index 6e6b2a8e0..3d835dce2 100644 --- a/extractor/src/test/java/org/schabi/newpipe/extractor/utils/UtilsTest.java +++ b/extractor/src/test/java/org/schabi/newpipe/extractor/utils/UtilsTest.java @@ -3,13 +3,14 @@ package org.schabi.newpipe.extractor.utils; import org.junit.jupiter.api.Test; import org.schabi.newpipe.extractor.exceptions.ParsingException; +import javax.annotation.Nonnull; import java.util.Arrays; import static org.junit.jupiter.api.Assertions.assertEquals; -public class UtilsTest { +class UtilsTest { @Test - public void testMixedNumberWordToLong() throws ParsingException { + void testMixedNumberWordToLong() throws ParsingException { assertEquals(10, Utils.mixedNumberWordToLong("10")); assertEquals(10.5e3, Utils.mixedNumberWordToLong("10.5K"), 0.0); assertEquals(10.5e6, Utils.mixedNumberWordToLong("10.5M"), 0.0); @@ -18,13 +19,13 @@ public class UtilsTest { } @Test - public void testJoin() { + void testJoin() { assertEquals("some,random,stuff", Utils.join(",", Arrays.asList("some", "random", "stuff"))); assertEquals("some,random,not-null,stuff", Utils.nonEmptyAndNullJoin(",", new String[]{"some", "null", "random", "", "not-null", null, "stuff"})); } @Test - public void testGetBaseUrl() throws ParsingException { + void testGetBaseUrl() throws ParsingException { assertEquals("https://www.youtube.com", Utils.getBaseUrl("https://www.youtube.com/watch?v=Hu80uDzh8RY")); assertEquals("vnd.youtube", Utils.getBaseUrl("vnd.youtube://www.youtube.com/watch?v=jZViOEv90dI")); assertEquals("vnd.youtube", Utils.getBaseUrl("vnd.youtube:jZViOEv90dI")); @@ -33,7 +34,7 @@ public class UtilsTest { } @Test - public void testFollowGoogleRedirect() { + void testFollowGoogleRedirect() { assertEquals("https://www.youtube.com/watch?v=Hu80uDzh8RY", Utils.followGoogleRedirectIfNeeded("https://www.google.it/url?sa=t&rct=j&q=&esrc=s&cd=&cad=rja&uact=8&url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DHu80uDzh8RY&source=video")); assertEquals("https://www.youtube.com/watch?v=0b6cFWG45kA", @@ -46,4 +47,52 @@ public class UtilsTest { assertEquals("https://www.youtube.com/watch?v=Hu80uDzh8RY&url=hello", Utils.followGoogleRedirectIfNeeded("https://www.youtube.com/watch?v=Hu80uDzh8RY&url=hello")); } + + @Test + void dashManifestCreatorCacheTest() { + final ManifestCreatorCache cache = new ManifestCreatorCache<>(); + cache.setMaximumSize(30); + setCacheContent(cache); + // 30 elements set -> cache resized to 23 -> 5 new elements set to the cache -> 28 + assertEquals(28, cache.size(), + "Wrong cache size with default clear factor and 30 as the maximum size"); + + cache.reset(); + cache.setMaximumSize(20); + cache.setClearFactor(0.5); + + setCacheContent(cache); + // 30 elements set -> cache resized to 10 -> 5 new elements set to the cache -> 15 + assertEquals(15, cache.size(), + "Wrong cache size with 0.5 as the clear factor and 20 as the maximum size"); + + // Clear factor and maximum size getters tests + assertEquals(0.5, cache.getClearFactor(), + "Wrong clear factor gotten from clear factor getter"); + assertEquals(20, cache.getMaximumSize(), + "Wrong maximum cache size gotten from maximum size getter"); + + // Resetters tests + cache.resetMaximumSize(); + assertEquals(ManifestCreatorCache.DEFAULT_MAXIMUM_SIZE, cache.getMaximumSize(), + "Wrong maximum cache size gotten from maximum size getter after maximum size reset"); + + cache.resetClearFactor(); + assertEquals(ManifestCreatorCache.DEFAULT_CLEAR_FACTOR, cache.getClearFactor(), + "Wrong clear factor gotten from clear factor getter after clear factor reset"); + } + + private void setCacheContent(@Nonnull final ManifestCreatorCache cache) { + int i = 0; + while (i < 26) { + cache.put(Character.toString((char) (97 + i)), "V"); + ++i; + } + + i = 0; + while (i < 9) { + cache.put("a" + (char) (97 + i), "V"); + ++i; + } + } }