تحليل الإلتزامات وسحب الطلبات في Travis CI و Buddy و AppVeyor باستخدام PVS-Studio

صورة 11

بدءًا من الإصدار 7.04 ، يتمتع محلل PVS-Studio للغات C و C ++ على Linux و macOS بقدرة اختبار على التحقق من قائمة الملفات المحددة. باستخدام الوضع الجديد ، يمكنك تكوين المحلل للتحقق من الطلبات وسحب الطلبات. ستوضح لك هذه المقالة كيفية تكوين التحقق من قائمة ملفات مشروع GitHub المعدلة في أنظمة CI (التكامل المستمر) الشائعة مثل Travis CI و Buddy و AppVeyor.

وضع فحص قائمة الملفات


PVS-Studio هي أداة للكشف عن الأخطاء ونقاط الضعف المحتملة في الكود المصدري للبرامج المكتوبة بلغات C و C ++ و C # و Java. وهو يعمل على أنظمة 64 بت على أنظمة التشغيل Windows و Linux و macOS.

في الإصدار 7.04 من PVS-Studio لنظامي التشغيل Linux و macOS ، ظهر وضع التحقق من قائمة الملفات المصدر. يعمل هذا مع المشروعات التي يسمح نظام الإنشاء الخاص بها بإنشاء ملف compile_commands.json . هناك حاجة حتى يتسنى للمحلل استخراج معلومات حول تجميع هذه الملفات. إذا كان نظام الإنشاء الخاص بك لا يدعم إنشاء ملف compile_commands.json ، يمكنك محاولة إنشاء مثل هذا الملف باستخدام الأداة المساعدة Bear .

أيضًا ، يمكن استخدام وضع التحقق من قائمة الملفات مع سجل التتبع للأشرطة لبدء تشغيل برنامج التحويل البرمجي (تتبع pvs-studio-analyser). للقيام بذلك ، ستحتاج أولاً إلى تنفيذ تجميع كامل للمشروع وتتبعه حتى يقوم المحلل بجمع معلومات كاملة حول معلمات الترجمة لجميع الملفات التي تم اختبارها.

ومع ذلك ، يحتوي هذا الخيار على عيب كبير - ستحتاج إما إلى إجراء تتبع كامل لتجميع المشروع بأكمله في كل بداية ، مما يتناقض في حد ذاته مع فكرة التحقق من الالتزام بسرعة. أو ، إذا كانت نتيجة التتبع نفسها مخزنة مؤقتًا ، فقد لا يبدأ تشغيل أداة التحليل اللاحقة إذا تغيرت بنية تبعية الملفات المصدر بعد التتبع (على سبيل المثال ، تتم إضافة # تضمين جديد إلى أحد الملفات المصدر).

لذلك ، لا نوصي باستخدام وضع التحقق من قائمة الملفات مع سجل التتبع للتحقق من الإلتزامات أو طلبات السحب. إذا كان يمكنك القيام بالتجميع التزايدي عند التحقق من الالتزام ، ففكر في استخدام وضع التحليل التزايدي .

يتم حفظ قائمة الملفات المصدر للتحليل في ملف نصي ونقلها إلى محلل باستخدام المعلمة -S :

pvs-studio-analyzer analyze ... -f build/compile_commands.json -S check-list.txt 

في هذا الملف ، تتم الإشارة إلى المسارات النسبية أو المطلقة للملفات ، ويجب أن يكون كل ملف جديد في سطر جديد. لا يجوز تحديد أسماء الملفات فقط لتحليلها ، ولكن أيضًا نص مختلف. سوف يرى المحلل أن هذا ليس ملفًا ، وسوف يتجاهل الخط. يمكن أن يكون هذا مفيدًا للتعليق إذا تم تحديد الملفات يدويًا. ومع ذلك ، غالبًا ما يتم إنشاء قائمة بالملفات أثناء التحليل في CI ، على سبيل المثال ، قد تكون هذه ملفات من طلب التزام أو سحب.

الآن باستخدام هذا الوضع ، يمكنك التحقق من الكود الجديد بسرعة قبل دخوله إلى فرع التطوير الرئيسي. لكي يستجيب نظام التحقق لتحذيرات المحلل ، تمت إضافة علامة التحذير --indicate إلى الأداة المساعدة plog-Converter :

 plog-converter ... --indicate-warnings ... -o /path/to/report.tasks ... 

باستخدام هذه العلامة ، سيقوم المحول بإرجاع رمز غير صفري إذا كانت هناك تحذيرات في تقرير المحلل. باستخدام رمز الإرجاع ، يمكنك حظر ربط ما قبل الالتزام أو الالتزام أو السحب ، وعرض تقرير المحلل الذي تم إنشاؤه على الشاشة ، أو المشاركة ، أو الإرسال عبر البريد.

المذكرة. في المرة الأولى التي تقوم فيها بتشغيل تحليل قائمة الملفات ، سيتم تحليل المشروع بأكمله ، لأنه يحتاج المحلل إلى إنشاء ملف تبعيات ملفات مصدر المشروع من ملفات الرأس. هذه هي ميزة تحليل ملفات C و C ++. في المستقبل ، يمكن تخزين ملف التبعية مؤقتًا وسيتم تحديثه تلقائيًا بواسطة المحلل. تتمثل ميزة التحقق من الإلتزامات عند استخدام وضع التحقق من قائمة الملفات عبر استخدام وضع التحليل التزايدي في أنك تحتاج فقط إلى تخزين هذا الملف مؤقتًا ، وليس ملفات الكائنات.

المبادئ العامة لتحليل طلب السحب


يستغرق تحليل المشروع بأكمله الكثير من الوقت ، لذلك من المنطقي التحقق من جزء معين منه فقط. المشكلة هي أنك تحتاج إلى فصل الملفات الجديدة عن بقية ملفات المشروع.

النظر في مثال لشجرة الالتزام مع فرعين:

الصورة 5


دعونا نتخيل أن الالتزام A1 يحتوي على كمية كبيرة إلى حد ما من التعليمات البرمجية التي تم اختبارها بالفعل. قبل ذلك بقليل ، قمنا بإنشاء فرع من A1 الالتزام وتغيير بعض الملفات.

بالطبع ، لاحظت أنه بعد A1 كانت هناك إلتزمتان أخريان ، لكن هذه كانت أيضًا عمليات دمج للفروع الأخرى ، لأننا لا نلتزم بشكل رئيسي . والآن حان الوقت عندما يكون الإصلاح الجديد جاهزًا. لذلك ، ظهر طلب سحب لدمج B3 و A3 .

بالطبع ، يمكن للمرء التحقق من النتيجة الكاملة لعملية الدمج الخاصة بهم ، ولكن هذا سيكون طويلاً وغير مبرر ، حيث تم تغيير عدد قليل فقط من الملفات. لذلك ، يكون تحليل المتغير فقط أكثر كفاءة.

للقيام بذلك ، نحصل على الفرق بين الفروع ، في رأس الفرع الذي نريد دمجه في برنامج الماجستير:

 git diff --name-only HEAD origin/$MERGE_BASE > .pvs-pr.list 

$ MERGE_BASE سننظر في المزيد من التفاصيل لاحقًا. الحقيقة هي أن خدمة CI لا توفر كل المعلومات اللازمة حول أساس الدمج ، لذلك في كل مرة يتعين عليك التوصل إلى طرق جديدة للحصول على هذه البيانات. سيتم تفصيل ذلك أدناه في كل من خدمات الويب الموضحة.

لذلك ، حصلنا على الفرق بين الفروع ، أو بالأحرى ، قائمة بأسماء الملفات التي تم تغييرها. نحتاج الآن إلى إعطاء ملف .pvs-pr.list (قمنا بإعادة توجيه الإخراج إليه أعلاه) إلى المحلل:

 pvs-studio-analyzer analyze -j8 \ -o PVS-Studio.log \ -S .pvs-pr.list 

بعد التحليل ، نحتاج إلى تحويل ملف السجل (PVS-Studio.log) إلى تنسيق يسهل قراءته:

 plog-converter -t errorfile PVS-Studio.log --cerr -w 

سيقوم هذا الأمر بسرد الأخطاء في stderr (معيار إخراج رسالة خطأ الدفق).

الآن فقط نحن بحاجة ليس فقط لعرض الأخطاء ، ولكن أيضًا لإبلاغ خدمتنا بالتجميع واختبار المشاكل. للقيام بذلك ، تمت إضافة علامة -W ( - تحذيرات -تحذير ) إلى المحول. إذا كان هناك تحذير محلل واحد على الأقل ، فسوف يتغير رمز الإرجاع الخاص بأداة محول plog إلى 2 ، وهذا بدوره سيبلغ خدمة CI بوجود أخطاء محتملة في ملفات طلب السحب.

ترافيس سي


يتم التكوين كملف .travis.yml . للراحة ، أنصحك بوضع كل شيء في نص برمجي منفصل مع وظائف سيتم استدعاؤها من ملف .travis.yml ( bash script_name.sh function_name ).

سنضيف الكود الضروري إلى البرنامج النصي bash ، حتى نحصل على المزيد من الوظائف. في قسم التثبيت ، اكتب ما يلي:

 install: - bash .travis.sh travis_install 

إذا كان لديك أي تعليمات ، يمكنك نقلها إلى البرنامج النصي عن طريق إزالة الواصلات.

افتح الملف .travis.sh وأضف تثبيت المحلل إلى وظيفة travis_install () :

 travis_install() { wget -q -O - https://files.viva64.com/etc/pubkey.txt \ | sudo apt-key add - sudo wget -O /etc/apt/sources.list.d/viva64.list \ https://files.viva64.com/etc/viva64.list sudo apt-get update -qq sudo apt-get install -qq pvs-studio } 

الآن قم بإضافة التحليل إلى قسم البرنامج النصي :

 script: - bash .travis.sh travis_script 

وفي البرنامج النصي bash:

 travis_script() { pvs-studio-analyzer credentials $PVS_USERNAME $PVS_KEY if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then git diff --name-only origin/HEAD > .pvs-pr.list pvs-studio-analyzer analyze -j8 \ -o PVS-Studio.log \ -S .pvs-pr.list \ --disableLicenseExpirationCheck else pvs-studio-analyzer analyze -j8 \ -o PVS-Studio.log \ --disableLicenseExpirationCheck fi plog-converter -t errorfile PVS-Studio.log --cerr -w } 

يجب تشغيل هذا الرمز بعد بناء المشروع ، على سبيل المثال ، إذا كان لديك بناء على CMake:

 travis_script() { CMAKE_ARGS="-DCMAKE_EXPORT_COMPILE_COMMANDS=On ${CMAKE_ARGS}" cmake $CMAKE_ARGS CMakeLists.txt make -j8 } 

سوف تتحول مثل هذا:

 travis_script() { CMAKE_ARGS="-DCMAKE_EXPORT_COMPILE_COMMANDS=On ${CMAKE_ARGS}" cmake $CMAKE_ARGS CMakeLists.txt make -j8 pvs-studio-analyzer credentials $PVS_USERNAME $PVS_KEY if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then git diff --name-only origin/HEAD > .pvs-pr.list pvs-studio-analyzer analyze -j8 \ -o PVS-Studio.log \ -S .pvs-pr.list \ --disableLicenseExpirationCheck else pvs-studio-analyzer analyze -j8 \ -o PVS-Studio.log \ --disableLicenseExpirationCheck fi plog-converter -t errorfile PVS-Studio.log --cerr -w } 

ربما لاحظت بالفعل متغيرات البيئة المحددة $ TRAVIS_PULL_REQUEST و $ TRAVIS_BRANCH . تعلن شركة Travis CI عن نفسها:

  • $ TRAVIS_PULL_REQUEST يخزن رقم طلب السحب أو خطأ إذا كان فرعًا عاديًا ؛
  • يخزن TRAVIS_REPO_SLUG $ اسم مستودع المشروع.

خوارزمية هذه الوظيفة:

الصورة 7

يستجيب Travis CI لرموز الإرجاع ، وبالتالي فإن وجود تحذيرات سيُخبر الخدمة بتمييز الالتزام على أنه يحتوي على أخطاء.

الآن ، دعونا نلقي نظرة فاحصة على هذا السطر من التعليمات البرمجية:

 git diff --name-only origin/HEAD > .pvs-pr.list 

الحقيقة هي أن Travis CI يدمج الفروع تلقائيًا أثناء تحليل طلب السحب:

الصورة 8

لذلك ، نقوم بتحليل A4 ، وليس B3-> A3 . بسبب هذه الميزة ، نحتاج إلى حساب الاختلاف باستخدام A3 ، وهو بالتحديد الجزء العلوي للفرع من الأصل .

بقي أحد التفاصيل المهمة - التخزين المؤقت تبعيات ملفات الرأس على وحدات الترجمة المترجمة (* .c ، * .cc ، * .cpp ، وما إلى ذلك). يقوم المحلل بحساب هذه التبعيات في البداية في وضع التحقق من قائمة الملفات ثم حفظها في دليل .PVS-Studio. يسمح لك Travis CI بتخزين مجلدات التخزين المؤقت ، لذلك سنقوم بحفظ بيانات دليل .PVS-Studio / :

 cache: directories: - .PVS-Studio/ 

يجب إضافة هذا الرمز إلى ملف .travis.yml . يخزن هذا الدليل العديد من البيانات التي تم جمعها بعد التحليل ، مما سيسرع بشكل كبير عمليات الإطلاق اللاحقة لتحليل قائمة الملفات أو التحليل التزايدي. إذا لم يتم ذلك ، فسيحلل المحلل فعليًا جميع الملفات في كل مرة.

رفيق


مثل Travis CI ، يوفر Buddy القدرة على إنشاء واختبار المشروعات المخزنة على GitHub تلقائيًا. على عكس Travis CI ، يتم تكوينه في واجهة الويب (يتوفر دعم bash) ، لذلك ليست هناك حاجة لتخزين ملفات التكوين في المشروع.

بادئ ذي بدء ، نحتاج إلى إضافة إجراء جديد إلى خط التجميع:

الصورة 1

نشير إلى المترجم الذي تم استخدامه لإنشاء المشروع. انتبه إلى حاوية المرسى المثبتة في هذا الإجراء. على سبيل المثال ، هناك حاوية خاصة لدول مجلس التعاون الخليجي:

صورة 6

الآن تثبيت PVS-Studio والأدوات المساعدة الضرورية:

الصورة 2

أضف الأسطر التالية إلى المحرر:

 apt-get update && apt-get -y install wget gnupg jq wget -q -O - https://files.viva64.com/etc/pubkey.txt | apt-key add - wget -O /etc/apt/sources.list.d/viva64.list \ https://files.viva64.com/etc/viva64.list apt-get update && apt-get -y install pvs-studio 

انتقل الآن إلى علامة التبويب "تشغيل" (الرمز الأول) وأضف الكود التالي إلى الحقل المناسب للمحرر:

 pvs-studio-analyzer credentials $PVS_USERNAME $PVS_KEY if [ "$BUDDY_EXECUTION_PULL_REQUEST_NO" != '' ]; then PULL_REQUEST_ID="pulls/$BUDDY_EXECUTION_PULL_REQUEST_NO" MERGE_BASE=`wget -qO - \ https://api.github.com/repos/${BUDDY_REPO_SLUG}/${PULL_REQUEST_ID} \ | jq -r ".base.ref"` git diff --name-only HEAD origin/$MERGE_BASE > .pvs-pr.list pvs-studio-analyzer analyze -j8 \ -o PVS-Studio.log \ --disableLicenseExpirationCheck \ -S .pvs-pr.list else pvs-studio-analyzer analyze -j8 \ -o PVS-Studio.log \ --disableLicenseExpirationCheck fi plog-converter -t errorfile PVS-Studio.log --cerr -w 

إذا قرأت القسم الخاص بـ Travs-CI ، فهذا الرمز مألوف لك بالفعل ، ومع ذلك ، فقد ظهرت الآن مرحلة جديدة:

صورة 9

الحقيقة هي أننا نحلل الآن ليس نتيجة الدمج ، ولكن رئيس الفرع الذي تم منه طلب السحب:

الصورة 10

لذلك ، نحن في الالتزام المشروط B3 ونحتاج إلى الحصول على الفرق مع A3 :

 PULL_REQUEST_ID="pulls/$BUDDY_EXECUTION_PULL_REQUEST_NO" MERGE_BASE=`wget -qO - \ https://api.github.com/repos/${BUDDY_REPO_SLUG}/${PULL_REQUEST_ID} \ | jq -r ".base.ref"` git diff --name-only HEAD origin/$MERGE_BASE > .pvs-pr.list 

لتحديد A3 ، استخدم واجهة برمجة تطبيقات GitHub:

 https://api.github.com/repos/${USERNAME}/${REPO}/pulls/${PULL_REQUEST_ID} 

استخدمنا المتغيرات التالية التي يوفرها الأصدقاء:

  • BUDDY_EXECUTION_PULL_REQEUST_NO $ - رقم طلب السحب ؛
  • BUDDY_REPO_SLUG $ - مزيج من اسم المستخدم والمستودع (على سبيل المثال أقصى / اختبار).

الآن احفظ التغييرات باستخدام الزر أدناه وتمكين تحليل طلب السحب:

الصورة 3

على عكس Travis CI ، لا نحتاج إلى تحديد .pvs-studio للتخزين المؤقت ، لأن Buddy يقوم تلقائيًا بتخزين جميع الملفات تلقائيًا لعمليات الإطلاق اللاحقة. لذلك ، فإن آخر شيء تبقى هو حفظ تسجيل الدخول وكلمة المرور لبرنامج PVS-Studio في Buddy. بعد حفظ التغييرات ، سنعود إلى Pipeline. نحتاج إلى متابعة إعداد المتغيرات وإضافة تسجيل الدخول والمفتاح لـ PVS-Studio:

صورة 4

بعد ذلك ، سيؤدي ظهور طلب سحب جديد أو التزام إلى إجراء فحص. إذا كان الالتزام يحتوي على أخطاء ، فسيشير Buddy إلى ذلك في صفحة طلب السحب.

AppVeyor


يشبه إعداد AppVeyor برنامج Buddy ، حيث يحدث كل شيء في واجهة الويب ولا توجد حاجة لإضافة ملف * .yml إلى مستودع المشروع.

انتقل إلى علامة التبويب "الإعدادات" في نظرة عامة على المشروع:

صورة 12

قم بالتمرير في هذه الصفحة لأسفل وتشغيل حفظ ذاكرة التخزين المؤقت لإنشاء طلبات السحب:

صورة 18

انتقل الآن إلى علامة التبويب "البيئة" ، حيث نحدد الصورة للتجميع ومتغيرات البيئة الضرورية:

صورة 19

إذا قرأت الأقسام السابقة ، فأنت على دراية جيدة بهذين المتغيرين - PVS_KEY و PVS_USERNAME . إذا لم يكن الأمر كذلك ، فأنا أذكرك أنها ضرورية للتحقق من ترخيص محلل PVS-Studio. في المستقبل ، سنلتقي بهم مرة أخرى في البرامج النصية Bash.

في نفس الصفحة أدناه ، نشير إلى مجلد التخزين المؤقت:

صورة 15

إذا لم نفعل ذلك ، فسنقوم بتحليل المشروع بأكمله بدلاً من زوج من الملفات ، لكننا سنحصل على الإخراج من الملفات المحددة. لذلك ، من المهم إدخال اسم الدليل الصحيح.

الآن حان الوقت للتحقق من البرنامج النصي. افتح علامة التبويب الاختبارات واختر Script:

صورة 20

أدخل الكود التالي في هذا النموذج:

 sudo apt-get update && sudo apt-get -y install jq wget -q -O - https://files.viva64.com/etc/pubkey.txt \ | sudo apt-key add - sudo wget -O /etc/apt/sources.list.d/viva64.list \ https://files.viva64.com/etc/viva64.list sudo apt-get update && sudo apt-get -y install pvs-studio pvs-studio-analyzer credentials $PVS_USERNAME $PVS_KEY PWD=$(pwd -L) if [ "$APPVEYOR_PULL_REQUEST_NUMBER" != '' ]; then PULL_REQUEST_ID="pulls/$APPVEYOR_PULL_REQUEST_NUMBER" MERGE_BASE=`wget -qO - \ https://api.github.com/repos/${APPVEYOR_REPO_NAME}/${PULL_REQUEST_ID} \ | jq -r ".base.ref"` git diff --name-only HEAD origin/$MERGE_BASE > .pvs-pr.list pvs-studio-analyzer analyze -j8 \ -o PVS-Studio.log \ --disableLicenseExpirationCheck \ --dump-files --dump-log pvs-dump.log \ -S .pvs-pr.list else pvs-studio-analyzer analyze -j8 \ -o PVS-Studio.log \ --disableLicenseExpirationCheck fi plog-converter -t errorfile PVS-Studio.log --cerr -w 

انتبه إلى الجزء التالي من الكود:

 PWD=$(pwd -L) if [ "$APPVEYOR_PULL_REQUEST_NUMBER" != '' ]; then PULL_REQUEST_ID="pulls/$APPVEYOR_PULL_REQUEST_NUMBER" MERGE_BASE=`wget -qO - \ https://api.github.com/repos/${APPVEYOR_REPO_NAME}/${PULL_REQUEST_ID} \ | jq -r ".base.ref"` git diff --name-only HEAD origin/$MERGE_BASE > .pvs-pr.list pvs-studio-analyzer analyze -j8 \ -o PVS-Studio.log \ --disableLicenseExpirationCheck \ --dump-files --dump-log pvs-dump.log \ -S .pvs-pr.list else pvs-studio-analyzer analyze -j8 \ -o PVS-Studio.log \ --disableLicenseExpirationCheck fi 

يبدو أن التعيين المحدد لقيمة الأمر pwd للمتغير الذي يجب أن يخزن هذه القيمة الافتراضية غريب من النظرة الأولى ، ومع ذلك ، سأشرح كل شيء الآن.

أثناء تكوين المحلل في AppVeyor ، واجهت سلوك محلل غريب للغاية. من ناحية ، كان كل شيء يعمل بشكل صحيح ، لكن التحليل لم يبدأ. قضيت الكثير من الوقت لألاحظ أننا في الدليل / home / appveyor / projects / testcalc / ، والمحلل متأكد من أننا في / opt / appveyor / build-agent /. ثم أدركت أن المتغير PWD $ يكذب قليلاً. لهذا السبب ، قمت بتحديث قيمتها يدويًا قبل بدء التحليل.

ثم كل شيء ، كما كان من قبل:

صورة 17

فكر الآن في المقتطف التالي:

 PULL_REQUEST_ID="pulls/$APPVEYOR_PULL_REQUEST_NUMBER" MERGE_BASE=`wget -qO - \ https://api.github.com/repos/${APPVEYOR_REPO_NAME}/${PULL_REQUEST_ID} \ | jq -r ".base.ref"` 

في ذلك ، نحصل على الفرق بين الفروع التي تم إعلان طلب السحب عليها. للقيام بذلك ، نحتاج إلى متغيرات البيئة التالية:

  • APPVEYOR_PULL_REQUEST_NUMBER $ - عدد طلبات السحب ؛
  • APPVEYOR_REPO_NAME $ - اسم مستخدم المشروع ومستودعه.

استنتاج


بالطبع ، لم نفكر في جميع الخدمات الممكنة للتكامل المستمر ، ومع ذلك ، فكلها متشابهة إلى حد كبير مع خصائص العمل الأخرى. باستثناء التخزين المؤقت ، تصنع كل خدمة "الدراجة" الخاصة بها ، لذلك كل شيء مختلف دائمًا.

في مكان ما ، كما هو الحال في Travis-CI ، يعمل زوجان من التعليمات البرمجية وذاكرة التخزين المؤقت دون عيب ؛ في مكان ما ، كما هو الحال في AppVeyor ، تحتاج فقط إلى تحديد المجلد في الإعدادات ؛ ولكن في مكان ما تحتاج إلى إنشاء مفاتيح فريدة ومحاولة إقناع النظام لمنحك الفرصة للكتابة فوق الجزء المخزن مؤقتًا. لذلك ، إذا كنت ترغب في تكوين تحليل طلبات السحب على خدمة التكامل المستمر ، والتي لم تتم مناقشتها أعلاه ، فتأكد أولاً من أنك لن تواجه مشكلات في التخزين المؤقت.

شكرا لاهتمامكم إذا لم ينجح شيء ما ، فلا تتردد في الكتابة إلينا في الدعم . نحن سوف موجه ومساعدة.



إذا كنت ترغب في مشاركة هذه المقالة مع جمهور يتحدث الإنجليزية ، فالرجاء استخدام الرابط الخاص بالترجمة: Maxim Zvyagintsev. تحليل الإلتزامات وسحب الطلبات في Travis CI و Buddy و AppVeyor باستخدام PVS-Studio .

Source: https://habr.com/ru/post/ar470984/


All Articles