Skip to main content
عندما ترسل طلب سحب، يُجري نظام التكامل المستمر (CI) في ClickHouse بعض الفحوصات المؤتمتة على تعليماتك البرمجية. ويحدث ذلك بعد أن يراجع أحد مسؤولي صيانة المستودع (شخص من فريق ClickHouse) تعليماتك البرمجية ويضيف الوسم can be tested إلى طلب السحب الخاص بك. تُدرَج نتائج هذه الفحوصات في صفحة طلب السحب على GitHub، كما هو موضح في وثائق GitHub الخاصة بفحوصات الحالة. إذا أخفق أحد الفحوصات، فقد يُطلب منك إصلاحه. تقدم هذه الصفحة نظرة عامة على الفحوصات التي قد تواجهها، وما يمكنك فعله لإصلاحها. إذا بدا أن إخفاق الفحص غير مرتبط بتغييراتك، فقد يكون ذلك إخفاقًا عابرًا أو مشكلة في البنية التحتية. أرسل commit فارغًا إلى طلب السحب لإعادة تشغيل فحوصات CI:
إذا لم تكن متأكدًا مما ينبغي فعله، فاطلب المساعدة من أحد مشرفي المشروع.

الدمج مع master

يتحقق من أن طلب السحب يمكن دمجه في master. إذا لم يكن ذلك ممكنًا، فسيفشل مع الرسالة Cannot fetch mergecommit. لإصلاح هذا التحقق، حلّ التعارض كما هو موضح في وثائق GitHub، أو ادمج الفرع master في فرع طلب السحب باستخدام git.

فحص المستندات (Mintlify)

يتحقق من وثائق Mintlify والروابط والمراسي الداخلية وعمليات إعادة التوجيه واستيراد المقتطفات وسجلات التغييرات. ويُبلّغ عن إخفاقات الروابط الخارجية كتحذيرات. كما يرفض التعديلات المباشرة على الأقسام المُولَّدة ونسخ الوثائق للقراءة فقط. حدِّث الوثائق المهيكلة في تسجيل المصدر بدلاً من ذلك؛ ويجب أن تحمل التحديثات المُولَّدة المقصودة الوسم pr-autogenerated-docs. إذا أخفق الفحص بعد تغيير في الوثائق، فافتح تقريره وابحث عن رسائل ERROR وWARNING.

التحقق من الوصف

تحقق من أن وصف طلب السحب الخاص بك يتوافق مع القالب PULL_REQUEST_TEMPLATE.md. يجب عليك تحديد فئة في سجل التغييرات للتغيير الذي أجريته (على سبيل المثال، Bug Fix)، وكتابة رسالة واضحة للمستخدم تصف هذا التغيير من أجل CHANGELOG.md

صورة Docker

يبني صور Docker الخاصة بخادم ClickHouse وKeeper للتحقق من بنائها بشكل صحيح.

اختبارات مكتبة Docker الرسمية

يُشغِّل اختبارات مكتبة Docker الرسمية للتحقق من أن صورة Docker ‏clickhouse/clickhouse-server تعمل على النحو الصحيح. لإضافة اختبارات جديدة، أنشئ دليلاً ci/jobs/scripts/docker_server/tests/$test_name وأضِف إليه البرنامج النصي run.sh. يمكن العثور على تفاصيل إضافية حول الاختبارات في توثيق البرامج النصية لمهام CI.

فحص Marker

يعني هذا الفحص أن نظام CI قد بدأ معالجة طلب السحب. وعندما تكون حالته ‘pending’، فهذا يعني أن بعض الفحوصات لم تبدأ بعد. وبعد بدء جميع الفحوصات، تتغير حالته إلى ‘success’.

Style check

يُجري فحوصات متنوعة للأسلوب على قاعدة الشيفرة. ويتوافق كل فحص فرعي أدناه مع testname في ci/jobs/check_style.py، ويمكن تشغيل كلٍّ منها على حدة باستخدام --test <name> (انظر أدناه).
cpp
فحوصات نمط C++ المستندة إلى Regex عبر check_cpp.sh. إذا فشل، فأصلِح المشكلات وفقًا لـدليل نمط الشيفرة.
whitespace_check
يرصد المسافات المزدوجة بعد الفواصل في ++C إذا لم تكن جزءًا من محاذاة الأعمدة.
catch_all
يمنع استخدام catch (...) خارج المدمِّرات وmain ونقاط دخول fuzzer، إذ إن تجاهل استثناء غير معروف ليس آمنًا.
yamllint
يدقّق ملفات سير العمل بتنسيق YAML ضمن .github/ باستخدام .yamllint.
xmllint
يتحقق من صحة ملفات XML في tests/ وprograms/.
functional_tests_check
يفحص الاختبارات عديمة الحالة: يجب أن تستخدم الاستعلامات التي تُطبِّق عامل تصفية على event_date>= yesterday() بدلاً من today() (لتجنّب التذبذب حول منتصف الليل)، ويجب ألا تتضمن أسماء ملفات الاختبار fail.
test_numbers_check
يرصد فجوات كبيرة في ترقيم الاختبارات عديمة الحالة (tests/queries/0_stateless/<NNNNN>_*). يكشف عن الروابط الرمزية المعطّلة في المستودع.
متفرقات
فحوصات متنوعة للمستودع عبر various_checks.sh: يجب أن تستخدم الاستعلامات على system.query_log / system.parts / إلخ عامل تصفية حسب currentDatabase، ويجب أن تتضمن مسارات ZooKeeper الخاصة بـ Replicated*MergeTree بادئةً خاصة بكل اختبار، ويجب أن تحتوي أدلة اختبارات التكامل على __init__.py، وألا توجد علامات UTF BOM، وألا تكون ملفات المصدر/البيانات مفعّلًا عليها بتات التنفيذ، وألا تُستخدم وسوم :latest مع صور docker-compose التابعة لجهات خارجية، وغير ذلك.

تشغيل مهمة Style Check محليًا

يمكن تشغيل مهمة Style Check بالكامل محليًا داخل حاوية Docker باستخدام:
لتشغيل فحص معيّن (مثل فحص cpp):
تسحب هذه الأوامر صورة Docker ‏clickhouse/style-test وتشغّل المهمة ضمن بيئة حاويات. لا يلزم أي تبعيات أخرى سوى بايثون 3 وDocker.

تشغيل الاختبارات عديمة الحالة

قد يعمل ClickHouse المثبّت محليًا بإعداداته الافتراضية مع بعض حالات الاختبار، لكنه لا يستطيع تشغيل جميع استعلامات الاختبار بشكل صحيح. في CI، تُثبّت كل مهمة إعدادًا محددًا لـ ClickHouse (مثل تخزين S3 والنسخ المتماثلة المتوازية)، وقد يكون من الصعب إعادة ذلك يدويًا. لتجنّب ذلك، يمكنك إعادة تنفيذ أي مهمة CI محليًا باستخدام آلية التنسيق نفسها المستخدمة في CI، من دون الحاجة إلى أي إعداد يدوي.

المتطلبات الأساسية

  • بايثون 3 (المكتبة القياسية فقط)
  • Docker
ثبّت Docker على Ubuntu إذا لزم الأمر، ثم أعِد تسجيل الدخول:

تشغيل مهمة CI محليًا

اختر أي اسم مهمة من تقرير CI وشغّلها محليًا:
  • احرص دائمًا على وضع اسم الـ مهمة بين علامتَي اقتباس تمامًا كما يظهر في تقرير CI (إذ قد يحتوي على مسافات وفواصل)، مثل: "Stateless tests (amd_debug, parallel)". يضبط ذلك إعدادات ClickHouse نفسها ويشغّل الاختبارات نفسها المستخدمة في CI.
  • إن المعمارية ونوع البناء في اسم الـ مهمة (مثل amd_debug) هما تسميتان خاصتان بـ CI. وعند التشغيل محليًا، لا يكون لهما أي تأثير — إذ سيستخدم الـ مهمة أي ملف تنفيذي توفّره وعلى أي معمارية تعمل. يحدّد اسم الـ مهمة فقط إعدادات ClickHouse ومجموعة الاختبارات (ما لم يتم تجاوز ذلك باستخدام --test).
  • في CI، تُقسَّم الاختبارات الوظيفية إلى دفعات لتحسين استخدام الموارد. على سبيل المثال، يغطي كلٌّ من "Stateless tests (amd_debug, parallel)" و"Stateless tests (amd_debug, sequential)" النطاق الكامل معًا: تُشغَّل الاختبارات الآمنة للتوازي بشكل متزامن، بينما تُشغَّل بقية الاختبارات بشكل تسلسلي. ويقلّل هذا التقسيم الوقت الإجمالي في CI من خلال زيادة التوازي إلى أقصى حد ممكن. ولإعادة تنفيذ نطاق الاختبار الكامل محليًا، شغّل كلتا الدفعتين.
  • توجد أيضًا مهمة في CI باسم "Fast test" تشغّل نطاقًا محدودًا من الاختبارات الوظيفية للتحقق من الوظائف الأساسية في ClickHouse — وهي تستخدم build لا يتضمن جميع الوحدات الاختيارية، وتُعد أسرع طريقة لاكتشاف حالات التراجع. يمكنك تشغيلها محليًا بالطريقة نفسها. ضع ملف ClickHouse التنفيذي في أحد مسارات البحث الافتراضية (./ci/tmp/clickhouse, ./build/programs/clickhouse, أو ./clickhouse) — وإلا فستحاول الـ مهمة بناء ClickHouse أولًا:

تشغيل اختبارات محددة ضمن مهمة CI

باستخدام --test، تُعِدّ المهمة بيئة ClickHouse مطابقة لتلك المستخدمة في CI، لكنها تُشغِّل الاختبارات المحددة فقط:
  • يمكنك تمرير أسماء اختبارات متعددة:
  • نصيحة: إذا كان أي إعداد لـ ClickHouse مناسبًا وكنت تحتاج فقط إلى تشغيل اختبارات معيّنة، فاستخدم الاسم البديل functional بدلًا من اسم المهمة الكامل:

خيارات تخصيص إضافية

  • --path PATH — مسار مخصّص لملف ClickHouse التنفيذي. افتراضيًا، يبحث المشغّل بالترتيب التالي: ./ci/tmp/clickhouse, ./build/programs/clickhouse, ./clickhouse.
  • --count N — كرّر كل اختبار N مرة.
  • --workers N — تجاوز الحساب التلقائي لعدد العمّال المتوازيين المستند إلى سعة الجهاز.

التحقق من البناء

يبني ClickHouse ضمن تهيئات مختلفة لاستخدامه في الخطوات اللاحقة.

تشغيل عمليات البناء محليًا

يمكن تشغيل عملية البناء محليًا في بيئة مشابهة لبيئة CI باستخدام:
لا يلزم أي تبعيات أخرى سوى بايثون 3 وDocker.

مهام البناء المتاحة

أسماء مهام البناء هي نفسها تمامًا كما تظهر في تقرير CI: عمليات بناء AMD64:
  • Build (amd_debug) - بنية Debug مع الرموز
  • Build (amd_release) - بنية إصدار محسّنة
  • Build (amd_asan) - بنية Address Sanitizer
  • Build (amd_tsan) - بنية Thread Sanitizer
  • Build (amd_msan) - بنية Memory Sanitizer
  • Build (amd_ubsan) - بنية Undefined Behavior Sanitizer
  • Build (amd_binary) - بنية إصدار سريعة بدون Thin LTO
  • Build (amd_compat) - بنية توافق للأنظمة الأقدم
  • Build (amd_musl) - بنية باستخدام musl libc
  • Build (amd_darwin) - بنية macOS
  • Build (amd_freebsd) - بنية FreeBSD
عمليات بناء ARM64:
  • Build (arm_release) - بنية إصدار ARM64 محسّنة
  • Build (arm_asan) - بنية ARM64 لـ Address Sanitizer
  • Build (arm_coverage) - بنية ARM64 مع تضمين أدوات قياس التغطية
  • Build (arm_binary) - بنية إصدار ARM64 سريعة بدون Thin LTO
  • Build (arm_darwin) - بنية macOS لـ ARM64
  • Build (arm_v80compat) - بنية توافق ARMv8.0
معماريات أخرى:
  • Build (ppc64le) - PowerPC ‏64-بت بنظام Little Endian
  • Build (riscv64) - RISC-V ‏64-بت
  • Build (s390x) - IBM System/390 ‏64-بت
  • Build (loongarch64) - LoongArch ‏64-بت
  • Build (wasm64) - WebAssembly ‏64-بت، عبر Emscripten. تجريبي: يبني الملف التنفيذي clickhouse ويتحقق من أن clickhouse local ينفذ الاستعلامات ضمن Node.js ≥ 24 (تعمل الوحدة أيضًا في المتصفحات، لكن CI لا يتحقق من ذلك بعد)
إذا نجحت المهمة، فستتوفر نتائج البناء في الدليل <repo_root>/ci/tmp/build. ملاحظة: بالنسبة إلى عمليات البناء التي لا تندرج ضمن فئة “معماريات أخرى” (والتي تستخدم التجميع المتقاطع)، يجب أن تتطابق معمارية جهازك المحلي مع نوع البناء لإنتاج البنية المطلوبة بواسطة BUILD_JOB_NAME.

مثال

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

الاختبارات الوظيفية عديمة الحالة

يشغّل الاختبارات الوظيفية عديمة الحالة لملفات ClickHouse التنفيذية المبنية بإعدادات مختلفة — release وdebug ومع أدوات sanitizers وما إلى ذلك. اطّلع على التقرير لمعرفة الاختبارات التي تفشل، ثم أعد إنتاج الفشل محليًا كما هو موضح هنا. لاحظ أنه يجب استخدام إعداد البناء الصحيح لإعادة الإنتاج — فقد يفشل اختبار عند استخدام AddressSanitizer لكنه ينجح في Debug. نزّل الملف التنفيذي من صفحة فحوصات بناء CI، أو ابنِه محليًا. في طلبات السحب، لا تشغّل معظم مهام sanitizer التي ينتهي اسمها بـ selected tests مجموعة الاختبارات كاملةً. بل تشغّل الاختبارات المحددة للتغيير فقط: الاختبارات التي يضيفها طلب السحب أو يعدّلها، والاختبارات التي فشلت بالفعل في طلب السحب هذا، والاختبارات التي تغطي الأسطر المعدّلة وفقًا لقاعدة بيانات التغطية. تستمر مهام MSan/WasmEdge في تشغيل المجموعة الكاملة لأن اختبارات Wasm UDF الحالية غير متوافقة مع MSan. كما تُشغَّل المجموعة الكاملة في إعدادات debug والملف التنفيذي العادي، وتُختبر الإصدارات المبنية مع sanitizers بواسطة اختبار الإجهاد، ويشغّل فرع master المجموعة الكاملة في كل إعداد.

اختبارات التكامل

يشغّل اختبارات التكامل.

فحص التحقق من إصلاح الأخطاء

يتحقق من وجود اختبار جديد (وظيفي أو تكاملي)، أو من وجود اختبارات معدّلة تفشل عند استخدام الملف التنفيذي المبني من فرع master. يُفعَّل هذا الفحص عندما يكون طلب السحب موسومًا بالوسم “pr-bugfix”.

اختبار الإجهاد

يشغّل الاختبارات الوظيفية عديمة الحالة بالتوازي من عدة عملاء لاكتشاف الأخطاء المرتبطة بالتزامن. إذا أخفق:
  • أصلح أولاً جميع حالات فشل الاختبارات الأخرى؛
    • راجع التقرير للعثور على سجلات الخادم وتحقق منها بحثًا عن الأسباب المحتملة للخطأ.

التحقّق من التوافق

يتحقّق من أنّ الملف التنفيذي clickhouse يعمل على التوزيعات التي تستخدم إصدارات قديمة من libc. إذا فشل، فاطلب المساعدة من أحد مسؤولي الصيانة.

AST fuzzer

يشغّل استعلامات مُولَّدة عشوائيًا لاكتشاف أخطاء في البرنامج. إذا تعذّر عمله، فاطلب المساعدة من أحد مسؤولي الصيانة.

اختبارات الأداء

لقياس التغيّرات في أداء الاستعلامات. هذا هو أطول فحص، ويستغرق تشغيله أقل بقليل من 6 ساعات. يُوضَّح تقرير اختبار الأداء بالتفصيل هنا.

التراجع عن انحدارات CI

هذا ليس فحصًا لطلب السحب الخاص بك؛ فهو يعمل على master كل ساعة، وقد يتراجع عن طلب سحب دُمج بالفعل. تأخذ المهمة الاختبارات الفاشلة التي سجلتها قاعدة بيانات CI على master خلال آخر 24 ساعة، وتجمعها حسب اسم الاختبار عبر جميع الفحوصات التي فشل فيها الاختبار. ويُعد فشل الاختبار نفسه في بناء التصحيح وبناء tsan فشلًا واحدًا له سبب واحد ينبغي البحث عنه، وتُدرج الفحوصات التي ظهر فيها ضمن التحقيق كأدلة؛ إذ إن التغيير الذي يعطّل اختبارًا يعطّله عادةً في عدة عمليات بناء في الوقت نفسه. وتُستبعد حالات الفشل التي لا تُنسب إلى أي اختبار، مثل فشل عملية بناء أو مهمة نفد وقتها؛ إذ لا توجد إجابة واحدة يمكن التراجع عنها لسؤال: «لماذا يفشل هذا الفحص؟». وبالمثل، تُرفض الصفوف التي تكتبها أداة الاختبار عن البرنامج النصي بأكمله تحت اسم يشبه اسم اختبار، مثل Test script failed أو Server died. ويُحال الاختبار الذي فشل في أكثر من commit على master إلى وكيل ذكاء اصطناعي، يُمنح المستودع مع السجل الكامل لـ master ووصولًا للقراءة فقط إلى قاعدة بيانات CI، ويجيب عن سؤال واحد: هل تسبب طلب سحب دُمج مؤخرًا في هذا الفشل، وأي طلب هو؟ لا يملك الوكيل أي بيانات اعتماد لـ GitHub ولا وسيلة لإنشائها — فهو يعمل كمستخدم مستقل غير ذي امتيازات، في بيئة فارغة، مع حجب نقاط نهاية بيانات اعتماد السحابة بجدار ناري لهذا المستخدم — ويعمل في نسخة مستنسخة مؤقتة من المستودع بدلًا من عملية checkout الخاصة بالمهمة؛ لذا لا يمكن لأي استنتاج يتوصل إليه — ولا لأي شيء قد يتركه خلفه — أن يصل إلى GitHub إلا عبر الفحوصات أدناه. يحسب الحد عمليات commit بدلًا من صفوف الفشل، لذا فإن commit سيئًا واحدًا يفشل في ثلاث عمليات بناء يظل ظهورًا واحدًا، ولا يُتخذ إجراء بشأنه. كما تُحسب حالات الفشل لكل نمط فشل؛ إذ تُبصم المخرجات المسجلة بعد استبعاد الأجزاء المتغيرة (العناوين والطوابع الزمنية وأسماء قواعد البيانات العشوائية)، ولا يُعد الاختبار الذي يمتد اسمه إلى سببين مختلفين — انحدار في commit وحالة تذبذب غير مرتبطة في commit آخر — فشلًا متكررًا، لذلك لا يُجرى أي تحقيق حتى يتكرر سبب واحد بمفرده. لا يؤدي إلى إجراء إلا جواب لا لبس فيه. عندما يبلغ الوكيل عن انحدار بثقة عالية، ويجتاز طلب السحب المذكور فحوصات السلامة (دُمج في master خلال الأيام الثلاثة الماضية، وليس تراجعًا بحد ذاته، ولم يُتراجع عنه بالفعل، ويمكن تطبيق التراجع دون تعارض)، تتراجع المهمة عنه، وتدمج التراجع فورًا دون انتظار الفحوصات، وتفتح طلب سحب مسودة بعنوان Reapply "..." يعيد إدخال التغيير. يجب أن يحدد حكم الانحدار كلاً من طلب السحب وcommit master الذي أُدخل التغيير عبره، ويجب أن يتطابقا؛ إذ تتحقق المهمة من الرقم مقابل سجل GitHub لcommit الدمج الذي نتج عن طلب السحب، ولا تتخذ إجراءً بشأن أي منهما إذا اختلفا. لا يُتراجع عن شيء بعد زوال الفشل؛ إذ يبقى الفشل في نافذة المراقبة ليوم كامل بعد توقفه، لذلك تستعلم المهمة من قاعدة بيانات CI مرة أخرى قبيل التراجع، ويُسجل الفشل الغائب عن أحدث عمليات commit master التي شغّل عليها كل فحص متأثر على أنه أُصلح بالفعل ويُترك وشأنه. وتُحدد أحدث عمليات commit وفق سجل الفرع نفسه، لا وفق وقت تشغيل فحوصاتها — إذ يجب ألا يُعامل commit قديم بدأ فحصه متأخرًا كدليل نجاح حديث. ويُعتمد الغياب بدلًا من النجاح، لأن معظم ما تحققه هذه المهمة لا يتضمن صف نجاح يمكن العثور عليه؛ إذ يُسجل الخطأ المنطقي أو الفحص المعلّق تحت نص الفشل نفسه، وفقط عند حدوثه. ولا يُحتسب أن فحصًا اختبر commit إلا عندما يُكمل تشغيله اختباراته: فالتشغيل الذي أُوقف في منتصفه — والذي تسجله الأداة باسم Test script failed أو Server died بجوار صفوف الاختبار التي أنتجها — نفذ بعض الاختبارات، وليس بالضرورة هذا الاختبار، لذا فإن غياب الفشل عنه ليس دليلًا، بينما تكون إعادة تشغيل الفحص نفسه التي اكتملت على commit نفسه دليلًا. ويعتمد مقدار الغياب الذي يُعتد به على مدى تكرار الفشل — فcommitان نظيفان لا يعنيان شيئًا لفشل يحدث في تشغيل واحد من كل مئة، لذا يجب أن تتجاوز المدة أطول فترة مسجلة اختفى فيها الفشل بين مرات حدوثه. عندما يتعذر الإجابة عن السؤال أصلًا — كأن فحصًا ظهر فيه الفشل لم يعد يُبلغ عنه بهذا الاسم، أو أن سجل عمليات commit منذ بدء الفشل أطول مما يعيده الاستعلام — يُسجل ذلك أيضًا ولا يُتراجع عن شيء. لا يُتراجع عن أكثر من طلبي سحب في كل تشغيل. إذا جرى التراجع عن طلب السحب الخاص بك:
  • يشرح طلب سحب التراجع ما الذي يفشل ولماذا نُسب السبب إلى التغيير. إذا كان الإسناد خاطئًا، فاذكر ذلك هناك وأعد التغيير.
  • يحتفظ طلب السحب المسودة Reapply "..." بتغييرك دون تعديل. أصلح الفشل في ذلك الفرع، وعلّمه بأنه جاهز للمراجعة، ودعه يمر عبر CI المعتاد.
يُسجَّل كل تحقيق في جدول checks_investigated ضمن قاعدة بيانات CI، بما في ذلك التحقيقات التي لا تؤدي إلى التراجع عن أي شيء. تُنقل القيم من checks كما سُجِّلت فيه، لذا يمكن ربط الجدولين مجددًا — مباشرةً عبر test_name، وباستخدام has(check_names, check_name) وhas(commit_shas, commit_sha) للأعمدة التي تجمع عدة صفوف من checks في مصفوفة، وباستخدام offending_pull_request_number = pull_request_number لطلب السحب المسؤول — كما يمكن الاستعلام على play.clickhouse.com عن سجل ما فحصته المهمة، وما خلصت إليه، وما نفذته:
تُنفَّذ المهمة في ci/jobs/revert_ci_regressions.py وتعمل ضمن سير العمل Hourly. يؤدي تشغيلها باستخدام --dry-run إلى فحص وتقييم جميع الشروط دون تغيير أي شيء: فلا جدول ولا صفوف ولا فرع ولا طلب سحب ولا دمج؛ بل تُطبع الصفوف التي كانت ستُكتب بدلًا من ذلك. يتولى سير عمل منفصل، .github/workflows/revert_broken_prs.yml، التراجع عن عمليات الدمج التي أُجريت رغم فشل CI الخاصة بها؛ ويستخدم كلاهما اسم الفرع نفسه revert-<pull request number>، لذا لا يُتراجع عن طلب السحب مرتين. ويُؤخذ التراجع الذي يبدأ يدويًا في الحسبان أيضًا: تتوقف المهمة عندما يكون التراجع موجودًا بالفعل على master، أو عند وجود فرع باسم revert-<pull request number> أو revert-<pull request number>-<branch> (وهو ما ينشئه زر Revert على GitHub)، أو عندما يكون طلب سحب من هذا الفرع مفتوحًا أو مدموجًا.
آخر تعديل في ١٨ أغسطس ٢٠٢٦